Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,74 @@
project(storage)
set(SRC
country.hpp
country_decl.cpp
country_decl.hpp
country_info_getter.cpp
country_info_getter.hpp
country_name_getter.cpp
country_name_getter.hpp
country_info_reader_light.cpp
country_info_reader_light.hpp
country_parent_getter.cpp
country_parent_getter.hpp
country_tree.cpp
country_tree.hpp
country_tree_helpers.cpp
country_tree_helpers.hpp
diff_scheme/apply_diff.cpp
diff_scheme/apply_diff.hpp
diff_scheme/diffs_data_source.cpp
diff_scheme/diffs_data_source.hpp
diff_scheme/diff_scheme_loader.cpp
diff_scheme/diff_scheme_loader.hpp
diff_scheme/diff_types.hpp
downloader.hpp
downloader_search_params.hpp
downloader_queue_interface.hpp
downloader_queue_universal.cpp
downloader_queue_universal.hpp
downloading_policy.cpp
downloading_policy.hpp
map_files_downloader.cpp
map_files_downloader.hpp
map_files_downloader_with_ping.cpp
map_files_downloader_with_ping.hpp
queued_country.cpp
queued_country.hpp
pinger.cpp
pinger.hpp
routing_helpers.cpp
routing_helpers.hpp
storage.cpp
storage.hpp
storage_defines.cpp
storage_defines.hpp
storage_helpers.cpp
storage_helpers.hpp
)
if (PLATFORM_IPHONE)
append(SRC
background_downloading/downloader_adapter_ios.h
background_downloading/downloader_adapter_ios.mm
background_downloading/downloader_queue.hpp
)
else()
append(SRC
http_map_files_downloader.cpp
http_map_files_downloader.hpp
)
endif()
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
platform
coding
mwm_diff
cppjansson
)
omim_add_test_subdirectory(storage_tests)
omim_add_test_subdirectory(storage_integration_tests)

View file

@ -0,0 +1,28 @@
#pragma once
#include "storage/background_downloading/downloader_queue.hpp"
#include "storage/map_files_downloader_with_ping.hpp"
namespace storage
{
class BackgroundDownloaderAdapter : public MapFilesDownloaderWithPing
{
public:
// MapFilesDownloader overrides:
void Remove(CountryId const & countryId) override;
void Clear() override;
QueueInterface const & GetQueue() const override;
private:
// MapFilesDownloaderWithServerList overrides:
void Download(QueuedCountry && queuedCountry) override;
// Trying to download mwm from different servers recursively.
void DownloadFromLastUrl(CountryId const & countryId, std::string const & downloadPath,
std::vector<std::string> && urls);
BackgroundDownloaderQueue<uint64_t> m_queue;
};
} // namespace storage

View file

@ -0,0 +1,137 @@
#import "storage/background_downloading/downloader_adapter_ios.h"
#import "platform/background_downloader_ios.h"
#include "storage/downloader.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include <memory>
#include <utility>
@interface NSError (ToDownloaderError)
- (downloader::DownloadStatus)toDownloaderError;
@end
@implementation NSError (ToDownloaderError)
- (downloader::DownloadStatus)toDownloaderError {
return self.code == NSURLErrorFileDoesNotExist ? downloader::DownloadStatus::FileNotFound
: downloader::DownloadStatus::Failed;
}
@end
namespace storage
{
void BackgroundDownloaderAdapter::Remove(CountryId const & countryId)
{
MapFilesDownloader::Remove(countryId);
if (!m_queue.Contains(countryId))
return;
BackgroundDownloader * downloader = [BackgroundDownloader sharedBackgroundMapDownloader];
auto const taskIdentifier = m_queue.GetTaskInfoForCountryId(countryId);
if (taskIdentifier)
[downloader cancelTaskWithIdentifier:*taskIdentifier];
m_queue.Remove(countryId);
}
void BackgroundDownloaderAdapter::Clear()
{
MapFilesDownloader::Clear();
BackgroundDownloader * downloader = [BackgroundDownloader sharedBackgroundMapDownloader];
[downloader clear];
m_queue.Clear();
}
QueueInterface const & BackgroundDownloaderAdapter::GetQueue() const
{
if (m_queue.IsEmpty())
return MapFilesDownloader::GetQueue();
return m_queue;
}
void BackgroundDownloaderAdapter::Download(QueuedCountry && queuedCountry)
{
if (!IsDownloadingAllowed())
{
queuedCountry.OnDownloadFinished(downloader::DownloadStatus::Failed);
return;
}
auto const countryId = queuedCountry.GetCountryId();
auto urls = MakeUrlList(queuedCountry.GetRelativeUrl());
// Get urls order from worst to best.
std::reverse(urls.begin(), urls.end());
auto const path = queuedCountry.GetFileDownloadPath();
// The order is important here: add to the queue first, notify start downloading then.
// Infinite recursion possible here, otherwise:
// OnStartDownloading -> NotifyStatusChanged -> processCountryEvent -> configDialog (?!)
// -> downloadNode for the same country if autodownload enabled -> Download.
m_queue.Append(QueuedCountry(queuedCountry));
queuedCountry.OnStartDownloading();
DownloadFromLastUrl(countryId, path, std::move(urls));
}
void BackgroundDownloaderAdapter::DownloadFromLastUrl(CountryId const & countryId,
std::string const & downloadPath,
std::vector<std::string> && urls)
{
if (urls.empty())
return;
NSURL * url = [NSURL URLWithString:@(urls.back().c_str())];
assert(url != nil);
urls.pop_back();
auto onFinish = [this, countryId, downloadPath, urls = std::move(urls)](NSError *error) mutable
{
downloader::DownloadStatus status = error ? [error toDownloaderError] : downloader::DownloadStatus::Completed;
if (!m_queue.Contains(countryId))
return;
if (status == downloader::DownloadStatus::Failed && !urls.empty())
{
DownloadFromLastUrl(countryId, downloadPath, std::move(urls));
}
else
{
auto const country = m_queue.GetCountryById(countryId);
m_queue.Remove(countryId);
country.OnDownloadFinished(status);
}
};
auto onProgress = [this, countryId](int64_t totalWritten, int64_t totalExpected)
{
if (!m_queue.Contains(countryId))
return;
auto const & country = m_queue.GetCountryById(countryId);
country.OnDownloadProgress({totalWritten, totalExpected});
};
BackgroundDownloader * downloader = [BackgroundDownloader sharedBackgroundMapDownloader];
NSUInteger taskId = [downloader downloadWithUrl:url completion:onFinish progress:onProgress];
m_queue.SetTaskInfoForCountryId(countryId, taskId);
}
std::unique_ptr<MapFilesDownloader> GetDownloader()
{
return std::make_unique<BackgroundDownloaderAdapter>();
}
} // namespace storage

View file

@ -0,0 +1,81 @@
#pragma once
#include "storage/downloader_queue_interface.hpp"
#include "storage/queued_country.hpp"
#include "storage/storage_defines.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
namespace storage
{
template <typename TaskInfo>
class BackgroundDownloaderQueue : public QueueInterface
{
public:
using ForEachTaskInfoTypeFunction = std::function<void(TaskInfo const & info)>;
bool IsEmpty() const override { return m_queue.empty(); }
size_t Count() const override { return m_queue.size(); }
bool Contains(CountryId const & country) const override { return m_queue.find(country) != m_queue.cend(); }
void ForEachCountry(ForEachCountryFunction const & fn) const override
{
for (auto const & item : m_queue)
fn(item.second.m_queuedCountry);
}
void ForEachTaskInfo(ForEachTaskInfoTypeFunction const & fn) const
{
for (auto const & item : m_queue)
if (item.second.m_taskInfo)
fn(*item.second.m_taskInfo);
}
void Append(QueuedCountry && country)
{
auto const countryId = country.GetCountryId();
auto const result = m_queue.emplace(countryId, std::move(country));
result.first->second.m_queuedCountry.OnCountryInQueue();
}
void SetTaskInfoForCountryId(CountryId const & countryId, TaskInfo const & taskInfo)
{
auto const it = m_queue.find(countryId);
CHECK(it != m_queue.cend(), ());
it->second.m_taskInfo = taskInfo;
}
std::optional<TaskInfo> GetTaskInfoForCountryId(CountryId const & countryId) const
{
auto const it = m_queue.find(countryId);
if (it == m_queue.cend())
return {};
return it->second.m_taskInfo;
}
QueuedCountry & GetCountryById(CountryId const & countryId) { return m_queue.at(countryId).m_queuedCountry; }
void Remove(CountryId const & countryId) { m_queue.erase(countryId); }
void Clear() { m_queue.clear(); }
private:
struct TaskData
{
explicit TaskData(QueuedCountry && country) : m_queuedCountry(std::move(country)) {}
QueuedCountry m_queuedCountry;
std::optional<TaskInfo> m_taskInfo;
};
std::unordered_map<CountryId, TaskData> m_queue;
};
} // namespace storage

69
libs/storage/country.hpp Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include "storage/country_decl.hpp"
#include "storage/storage_defines.hpp"
#include "platform/local_country_file.hpp"
#include "platform/country_defines.hpp"
#include "geometry/rect2d.hpp"
#include "defines.hpp"
#include <cstdint>
#include <map>
#include <string>
#include <vector>
namespace storage
{
/// This class keeps all the information about a country in country tree (CountryTree).
/// It is guaranteed that every node represent a unique region has a unique |m_name| in country
/// tree.
/// If several nodes have the same |m_name| they represent the same region.
/// It happends in case of disputed territories.
/// That means that
/// * if several leaf nodes have the same |m_name| one mwm file corresponds
/// to all of them
/// * if several expandable (not leaf) nodes have the same |m_name|
/// the same hierarchy, the same set of mwm files and the same attributes correspond to all of them.
/// So in most cases it's enough to find the first inclusion of |Country| in country tree.
class Country
{
public:
Country() = default;
explicit Country(CountryId const & name, CountryId const & parent = kInvalidCountryId)
: m_name(name)
, m_parent(parent)
{}
void SetFile(platform::CountryFile && file) { m_file = std::move(file); }
void SetSubtreeAttrs(MwmCounter subtreeMwmNumber, MwmSize subtreeMwmSizeBytes)
{
m_subtreeMwmNumber = subtreeMwmNumber;
m_subtreeMwmSizeBytes = subtreeMwmSizeBytes;
}
MwmCounter GetSubtreeMwmCounter() const { return m_subtreeMwmNumber; }
MwmSize GetSubtreeMwmSizeBytes() const { return m_subtreeMwmSizeBytes; }
platform::CountryFile const & GetFile() const { return m_file; }
CountryId const & Name() const { return m_name; }
CountryId const & GetParent() const { return m_parent; }
private:
/// Name in the country node tree. In single mwm case it's a country id.
CountryId m_name;
/// Country id of parent of m_name in country tree. m_parent == kInvalidCountryId for the root.
CountryId m_parent;
/// |m_file| is a CountryFile of mwm with id == |m_name|.
/// if |m_name| is node id of a group of mwms, |m_file| is empty.
platform::CountryFile m_file;
/// The number of descendant mwm files of |m_name|. Only files (leaves in tree) are counted.
/// If |m_name| is a mwm file name |m_childrenNumber| == 1.
MwmCounter m_subtreeMwmNumber = 0;
/// Size of descendant mwm files of |m_name|.
/// If |m_name| is a mwm file name |m_subtreeMwmSizeBytes| is equal to size of the mwm.
MwmSize m_subtreeMwmSizeBytes = 0;
};
} // namespace storage

View file

@ -0,0 +1,36 @@
#include "storage/country_decl.hpp"
#include <cstddef>
using namespace std;
namespace storage
{
// static
void CountryInfo::FileName2FullName(string & fName)
{
size_t const i = fName.find('_');
if (i != string::npos)
{
// replace '_' with ", "
fName[i] = ',';
fName.insert(i + 1, " ");
}
}
// static
void CountryInfo::FullName2GroupAndMap(string const & fName, string & group, string & map)
{
size_t pos = fName.find(',');
if (pos == string::npos)
{
map = fName;
group.clear();
}
else
{
map = fName.substr(pos + 2);
group = fName.substr(0, pos);
}
}
} // namespace storage

View file

@ -0,0 +1,66 @@
#pragma once
#include "storage/storage_defines.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/point_coding.hpp"
#include "coding/read_write_utils.hpp"
#include "coding/varint.hpp"
#include "geometry/rect2d.hpp"
#include <cstdint>
#include <string>
#include <utility>
namespace storage
{
/// File name without extension (equal to english name - used in search for region).
struct CountryDef
{
CountryDef() = default;
CountryDef(CountryId const & countryId, m2::RectD const & rect) : m_countryId(countryId), m_rect(rect) {}
CountryId m_countryId;
m2::RectD m_rect;
};
struct CountryInfo
{
CountryInfo() = default;
CountryInfo(std::string const & id) : m_name(id) {}
// @TODO(bykoianko) Twine will be used intead of this function.
// So id (fName) will be converted to a local name.
static void FileName2FullName(std::string & fName);
static void FullName2GroupAndMap(std::string const & fName, std::string & group, std::string & map);
bool IsNotEmpty() const { return !m_name.empty(); }
/// Name (in native language) of country or region.
/// (if empty - equals to file name of country - no additional memory)
std::string m_name;
};
template <class Source>
void Read(Source & src, CountryDef & p)
{
rw::Read(src, p.m_countryId);
std::pair<int64_t, int64_t> r;
r.first = ReadVarInt<int64_t>(src);
r.second = ReadVarInt<int64_t>(src);
p.m_rect = Int64ToRectObsolete(r, serial::GeometryCodingParams().GetCoordBits());
}
template <class Sink>
void Write(Sink & sink, CountryDef const & p)
{
rw::Write(sink, p.m_countryId);
std::pair<int64_t, int64_t> const r = RectToInt64Obsolete(p.m_rect, serial::GeometryCodingParams().GetCoordBits());
WriteVarInt(sink, r.first);
WriteVarInt(sink, r.second);
}
} // namespace storage

View file

@ -0,0 +1,356 @@
#include "storage/country_info_getter.hpp"
#include "storage/country_decl.hpp"
#include "storage/country_tree.hpp"
#include "platform/local_country_file_utils.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/read_write_utils.hpp"
#include "geometry/mercator.hpp"
#include "geometry/region2d.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <functional>
#include <limits>
#include <utility>
namespace storage
{
namespace
{
size_t constexpr kInvalidId = std::numeric_limits<size_t>::max();
} // namespace
// CountryInfoGetterBase ---------------------------------------------------------------------------
CountryId CountryInfoGetterBase::GetRegionCountryId(m2::PointD const & pt) const
{
RegionId const id = FindFirstCountry(pt);
return id == kInvalidId ? kInvalidCountryId : m_countries[id].m_countryId;
}
bool CountryInfoGetterBase::BelongsToAnyRegion(m2::PointD const & pt, RegionIdVec const & regions) const
{
for (auto const & id : regions)
if (BelongsToRegion(pt, id))
return true;
return false;
}
bool CountryInfoGetterBase::BelongsToAnyRegion(CountryId const & countryId, RegionIdVec const & regions) const
{
for (auto const & id : regions)
if (m_countries[id].m_countryId == countryId)
return true;
return false;
}
CountryInfoGetterBase::RegionId CountryInfoGetterBase::FindFirstCountry(m2::PointD const & pt) const
{
for (size_t id = 0; id < m_countries.size(); ++id)
if (BelongsToRegion(pt, id))
return id;
return kInvalidId;
}
// CountryInfoGetter -------------------------------------------------------------------------------
std::vector<CountryId> CountryInfoGetter::GetRegionsCountryIdByRect(m2::RectD const & rect, bool rough) const
{
std::vector<CountryId> result;
for (size_t id = 0; id < m_countries.size(); ++id)
{
if (rect.IsRectInside(m_countries[id].m_rect))
{
result.push_back(m_countries[id].m_countryId);
}
else if (rect.IsIntersect(m_countries[id].m_rect))
{
if (rough || IsIntersectedByRegion(rect, id))
result.push_back(m_countries[id].m_countryId);
}
}
return result;
}
void CountryInfoGetter::GetRegionsCountryId(m2::PointD const & pt, CountriesVec & closestCoutryIds,
double lookupRadiusM) const
{
closestCoutryIds.clear();
m2::RectD const lookupRect = mercator::RectByCenterXYAndSizeInMeters(pt, lookupRadiusM);
for (size_t id = 0; id < m_countries.size(); ++id)
if (m_countries[id].m_rect.IsIntersect(lookupRect) && IsCloseEnough(id, pt, lookupRadiusM))
closestCoutryIds.emplace_back(m_countries[id].m_countryId);
}
void CountryInfoGetter::GetRegionInfo(m2::PointD const & pt, CountryInfo & info) const
{
RegionId const id = FindFirstCountry(pt);
if (id != kInvalidId)
GetRegionInfo(m_countries[id].m_countryId, info);
}
void CountryInfoGetter::GetRegionInfo(CountryId const & countryId, CountryInfo & info) const
{
auto const it = m_idToInfo.find(countryId);
if (it == m_idToInfo.end())
return;
info = it->second;
if (info.m_name.empty())
info.m_name = countryId;
CountryInfo::FileName2FullName(info.m_name);
}
void CountryInfoGetter::CalcUSALimitRect(m2::RectD rects[3]) const
{
auto fn = [&](CountryDef const & c)
{
if (c.m_countryId == "USA_Alaska")
rects[1] = c.m_rect;
else if (c.m_countryId == "USA_Hawaii")
rects[2] = c.m_rect;
else
rects[0].Add(c.m_rect);
};
ForEachCountry("USA_", fn);
}
m2::RectD CountryInfoGetter::CalcLimitRect(std::string const & prefix) const
{
m2::RectD rect;
ForEachCountry(prefix, [&rect](CountryDef const & c) { rect.Add(c.m_rect); });
return rect;
}
m2::RectD CountryInfoGetter::GetLimitRectForLeaf(CountryId const & leafCountryId) const
{
auto const it = m_countryIndex.find(leafCountryId);
if (it != m_countryIndex.end())
{
ASSERT_LESS(it->second, m_countries.size(), ());
return m_countries[it->second].m_rect;
}
else
{
// Full rect for World files.
return mercator::Bounds::FullRect();
}
}
void CountryInfoGetter::GetMatchedRegions(std::string const & affiliation, RegionIdVec & regions) const
{
// Once set, m_affiliations ptr is never changed (same as the content).
ASSERT(m_affiliations, ());
auto it = m_affiliations->find(affiliation);
if (it == m_affiliations->end())
return;
for (size_t i = 0; i < m_countries.size(); ++i)
if (binary_search(it->second.begin(), it->second.end(), m_countries[i].m_countryId))
regions.push_back(i);
}
void CountryInfoGetter::SetAffiliations(Affiliations const * affiliations)
{
m_affiliations = affiliations;
}
template <typename ToDo>
void CountryInfoGetter::ForEachCountry(std::string const & prefix, ToDo && toDo) const
{
for (auto const & country : m_countries)
if (country.m_countryId.starts_with(prefix))
toDo(country);
}
// CountryInfoReader -------------------------------------------------------------------------------
// static
std::unique_ptr<CountryInfoReader> CountryInfoReader::CreateCountryInfoReader(Platform const & platform)
{
try
{
CountryInfoReader * result =
new CountryInfoReader(platform.GetReader(PACKED_POLYGONS_FILE), platform.GetReader(COUNTRIES_FILE));
return std::unique_ptr<CountryInfoReader>(result);
}
catch (RootException const & e)
{
LOG(LCRITICAL, ("Can't load needed resources for storage::CountryInfoGetter:", e.Msg()));
}
return std::unique_ptr<CountryInfoReader>();
}
// static
std::unique_ptr<CountryInfoGetter> CountryInfoReader::CreateCountryInfoGetter(Platform const & platform)
{
return CreateCountryInfoReader(platform);
}
void CountryInfoReader::LoadRegionsFromDisk(size_t id, std::vector<m2::RegionD> & regions) const
{
regions.clear();
ReaderSource<ModelReaderPtr> src(m_reader.GetReader(strings::to_string(id)));
uint32_t const count = ReadVarUint<uint32_t>(src);
for (size_t i = 0; i < count; ++i)
{
std::vector<m2::PointD> points;
serial::LoadOuterPath(src, serial::GeometryCodingParams(), points);
regions.emplace_back(std::move(points));
}
}
CountryInfoReader::CountryInfoReader(ModelReaderPtr polyR, ModelReaderPtr countryR)
: m_reader(polyR)
, m_cache(3 /* logCacheSize */)
{
ReaderSource<ModelReaderPtr> src(m_reader.GetReader(PACKED_POLYGONS_INFO_TAG));
rw::Read(src, m_countries);
m_countryIndex.reserve(m_countries.size());
for (size_t i = 0; i < m_countries.size(); ++i)
m_countryIndex[m_countries[i].m_countryId] = i;
std::string buffer;
countryR.ReadAsString(buffer);
LoadCountryFile2CountryInfo(buffer, m_idToInfo);
}
void CountryInfoReader::ClearCachesImpl() const
{
std::lock_guard<std::mutex> lock(m_cacheMutex);
m_cache.ForEachValue([](std::vector<m2::RegionD> & v) { std::vector<m2::RegionD>().swap(v); });
m_cache.Reset();
}
template <typename Fn>
std::invoke_result_t<Fn, std::vector<m2::RegionD>> CountryInfoReader::WithRegion(size_t id, Fn && fn) const
{
std::lock_guard<std::mutex> lock(m_cacheMutex);
bool isFound = false;
auto & regions = m_cache.Find(static_cast<uint32_t>(id), isFound);
if (!isFound)
LoadRegionsFromDisk(id, regions);
return fn(regions);
}
bool CountryInfoReader::BelongsToRegion(m2::PointD const & pt, size_t id) const
{
if (!m_countries[id].m_rect.IsPointInside(pt))
return false;
auto contains = [&pt](std::vector<m2::RegionD> const & regions)
{
for (auto const & region : regions)
if (region.Contains(pt))
return true;
return false;
};
return WithRegion(id, contains);
}
bool CountryInfoReader::IsIntersectedByRegion(m2::RectD const & rect, size_t id) const
{
std::vector<std::pair<m2::PointD, m2::PointD>> const edges = {{rect.LeftTop(), rect.RightTop()},
{rect.RightTop(), rect.RightBottom()},
{rect.RightBottom(), rect.LeftBottom()},
{rect.LeftBottom(), rect.LeftTop()}};
auto contains = [&edges](std::vector<m2::RegionD> const & regions)
{
for (auto const & region : regions)
{
for (auto const & edge : edges)
{
m2::PointD result;
if (region.FindIntersection(edge.first, edge.second, result))
return true;
}
}
return false;
};
if (WithRegion(id, contains))
return true;
return BelongsToRegion(rect.Center(), id);
}
bool CountryInfoReader::IsCloseEnough(size_t id, m2::PointD const & pt, double distance) const
{
m2::RectD const lookupRect = mercator::RectByCenterXYAndSizeInMeters(pt, distance);
auto isCloseEnough = [&](std::vector<m2::RegionD> const & regions)
{
for (auto const & region : regions)
if (region.Contains(pt) || region.AtBorder(pt, lookupRect.SizeX() / 2))
return true;
return false;
};
return WithRegion(id, isCloseEnough);
}
// CountryInfoGetterForTesting ---------------------------------------------------------------------
CountryInfoGetterForTesting::CountryInfoGetterForTesting(std::vector<CountryDef> const & countries)
{
for (auto const & country : countries)
AddCountry(country);
}
void CountryInfoGetterForTesting::AddCountry(CountryDef const & country)
{
m_countries.push_back(country);
std::string const & name = country.m_countryId;
m_idToInfo[name].m_name = name;
}
void CountryInfoGetterForTesting::GetMatchedRegions(std::string const & affiliation, RegionIdVec & regions) const
{
for (size_t i = 0; i < m_countries.size(); ++i)
if (m_countries[i].m_countryId == affiliation)
regions.push_back(i);
}
void CountryInfoGetterForTesting::ClearCachesImpl() const {}
bool CountryInfoGetterForTesting::BelongsToRegion(m2::PointD const & pt, size_t id) const
{
CHECK_LESS(id, m_countries.size(), ());
return m_countries[id].m_rect.IsPointInside(pt);
}
bool CountryInfoGetterForTesting::IsIntersectedByRegion(m2::RectD const & rect, size_t id) const
{
CHECK_LESS(id, m_countries.size(), ());
return rect.IsIntersect(m_countries[id].m_rect);
}
bool CountryInfoGetterForTesting::IsCloseEnough(size_t id, m2::PointD const & pt, double distance) const
{
CHECK_LESS(id, m_countries.size(), ());
m2::RegionD rgn;
rgn.AddPoint(m_countries[id].m_rect.LeftTop());
rgn.AddPoint(m_countries[id].m_rect.RightTop());
rgn.AddPoint(m_countries[id].m_rect.RightBottom());
rgn.AddPoint(m_countries[id].m_rect.LeftBottom());
rgn.AddPoint(m_countries[id].m_rect.LeftTop());
m2::RectD const lookupRect = mercator::RectByCenterXYAndSizeInMeters(pt, distance);
return rgn.Contains(pt) || rgn.AtBorder(pt, lookupRect.SizeX() / 2);
}
} // namespace storage

View file

@ -0,0 +1,180 @@
#pragma once
#include "storage/country_decl.hpp"
#include "storage/storage_defines.hpp"
#include "platform/platform.hpp"
#include "geometry/point2d.hpp"
#include "geometry/region2d.hpp"
#include "coding/files_container.hpp"
#include "base/cache.hpp"
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace storage
{
// This class allows users to get information about country by point or by name.
class CountryInfoGetterBase
{
public:
// Identifier of a region (index in m_countries array).
using RegionId = size_t;
using RegionIdVec = std::vector<RegionId>;
virtual ~CountryInfoGetterBase() = default;
// Returns country file name without an extension for a country |pt|
// belongs to. If there is no such country, returns an empty
// string.
CountryId GetRegionCountryId(m2::PointD const & pt) const;
// Returns true when |pt| belongs to at least one of the specified
// |regions|.
bool BelongsToAnyRegion(m2::PointD const & pt, RegionIdVec const & regions) const;
// Returns true if there's at least one region with id equal to |countryId|.
bool BelongsToAnyRegion(CountryId const & countryId, RegionIdVec const & regions) const;
std::vector<CountryDef> const & GetCountries() const { return m_countries; }
protected:
// Returns identifier of the first country containing |pt| or |kInvalidId| if there is none.
RegionId FindFirstCountry(m2::PointD const & pt) const;
// Returns true when |pt| belongs to the country identified by |id|.
virtual bool BelongsToRegion(m2::PointD const & pt, size_t id) const = 0;
// List of all known countries.
std::vector<CountryDef> m_countries;
};
// *NOTE* This class is thread-safe.
class CountryInfoGetter : public CountryInfoGetterBase
{
public:
// Returns vector of countries file names without extension for
// countries belonging to |rect|. When |rough| is equal to true, the
// method is much faster but the result is less precise.
std::vector<CountryId> GetRegionsCountryIdByRect(m2::RectD const & rect, bool rough) const;
// Returns a list of country ids by a |pt| in mercator.
// |closestCoutryIds| is filled with country ids of mwms that cover |pt| or are close to it
// with the exception of World.mwm and custom user-provided mwms.
// The result may be empty, for example if |pt| is somewhere in an ocean.
void GetRegionsCountryId(m2::PointD const & pt, CountriesVec & closestCoutryIds,
double lookupRadiusM = 30000.0 /* 30 km */) const;
// Fills info for the region |pt| belongs to.
void GetRegionInfo(m2::PointD const & pt, CountryInfo & info) const;
// Fills info for the country by id.
void GetRegionInfo(CountryId const & countryId, CountryInfo & info) const;
// Fills limit rects of the USA:
// 0 - continental part
// 1 - Alaska
// 2 - Hawaii
void CalcUSALimitRect(m2::RectD rects[3]) const;
// Calculates the limit rect for all countries whose names start with |prefix|.
m2::RectD CalcLimitRect(std::string const & prefix) const;
// Returns the limit rect for |countryId| (non-expandable node).
// Returns the bounding box in mercator coordinates if |countryId| is a country id of
// a non-expandable node and zero rect otherwise.
m2::RectD GetLimitRectForLeaf(CountryId const & leafCountryId) const;
// Returns identifiers for all regions matching to |affiliation|.
virtual void GetMatchedRegions(std::string const & affiliation, RegionIdVec & regions) const;
// Clears the regions cache.
void ClearCaches() const { ClearCachesImpl(); }
void SetAffiliations(Affiliations const * affiliations);
protected:
CountryInfoGetter() = default;
// Invokes |toDo| on each country whose name starts with |prefix|.
template <typename ToDo>
void ForEachCountry(std::string const & prefix, ToDo && toDo) const;
// Clears regions cache.
virtual void ClearCachesImpl() const = 0;
// Returns true when |rect| intersects a country identified by |id|.
virtual bool IsIntersectedByRegion(m2::RectD const & rect, size_t id) const = 0;
// Returns true when the distance from |pt| to country identified by |id| is less than |distance|.
virtual bool IsCloseEnough(size_t id, m2::PointD const & pt, double distance) const = 0;
// @TODO(bykoianko): consider getting rid of m_countryIndex.
// Maps all leaf country id (file names) to their indices in m_countries.
std::unordered_map<CountryId, RegionId> m_countryIndex;
Affiliations const * m_affiliations = nullptr;
// Maps country file name without extension to a country info.
std::map<std::string, CountryInfo> m_idToInfo;
};
// This class reads info about countries from polygons file and
// countries file and caches it.
class CountryInfoReader : public CountryInfoGetter
{
public:
/// \returns CountryInfoReader/CountryInfoGetter based on countries.txt and packed_polygons.bin.
static std::unique_ptr<CountryInfoReader> CreateCountryInfoReader(Platform const & platform);
static std::unique_ptr<CountryInfoGetter> CreateCountryInfoGetter(Platform const & platform);
// Loads all regions for country number |id| from |m_reader|.
void LoadRegionsFromDisk(size_t id, std::vector<m2::RegionD> & regions) const;
protected:
CountryInfoReader(ModelReaderPtr polyR, ModelReaderPtr countryR);
// CountryInfoGetter overrides:
void ClearCachesImpl() const override;
bool BelongsToRegion(m2::PointD const & pt, size_t id) const override;
bool IsIntersectedByRegion(m2::RectD const & rect, size_t id) const override;
bool IsCloseEnough(size_t id, m2::PointD const & pt, double distance) const override;
template <typename Fn>
std::invoke_result_t<Fn, std::vector<m2::RegionD>> WithRegion(size_t id, Fn && fn) const;
FilesContainerR m_reader;
mutable base::Cache<uint32_t, std::vector<m2::RegionD>> m_cache;
mutable std::mutex m_cacheMutex;
};
// This class allows users to get info about very simply rectangular
// countries, whose info can be described by CountryDef instances.
// It's needed for testing purposes only.
class CountryInfoGetterForTesting : public CountryInfoGetter
{
public:
CountryInfoGetterForTesting() = default;
CountryInfoGetterForTesting(std::vector<CountryDef> const & countries);
void AddCountry(CountryDef const & country);
// CountryInfoGetter overrides:
void GetMatchedRegions(std::string const & affiliation, RegionIdVec & regions) const override;
protected:
// CountryInfoGetter overrides:
void ClearCachesImpl() const override;
bool BelongsToRegion(m2::PointD const & pt, size_t id) const override;
bool IsIntersectedByRegion(m2::RectD const & rect, size_t id) const override;
bool IsCloseEnough(size_t id, m2::PointD const & pt, double distance) const override;
};
} // namespace storage

View file

@ -0,0 +1,81 @@
#include "storage/country_info_reader_light.hpp"
#include "storage/country_decl.hpp"
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "coding/file_reader.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/read_write_utils.hpp"
#include "geometry/region2d.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <chrono>
#include <utility>
#include <vector>
#include "defines.hpp"
namespace lightweight
{
CountryInfoReader::CountryInfoReader()
{
try
{
m_reader = std::make_unique<FilesContainerR>(GetPlatform().GetReader(PACKED_POLYGONS_FILE));
ReaderSource<ModelReaderPtr> src(m_reader->GetReader(PACKED_POLYGONS_INFO_TAG));
rw::Read(src, m_countries);
}
catch (FileReader::Exception const & exception)
{
LOG(LERROR, ("Exception while reading file:", PACKED_POLYGONS_FILE, "reason:", exception.what()));
m_reader.reset();
m_countries.clear();
}
m_nameGetter.SetLocale(languages::GetCurrentTwine());
}
void CountryInfoReader::LoadRegionsFromDisk(size_t id, std::vector<m2::RegionD> & regions) const
{
regions.clear();
ReaderSource<ModelReaderPtr> src(m_reader->GetReader(strings::to_string(id)));
uint32_t const count = ReadVarUint<uint32_t>(src);
for (size_t i = 0; i < count; ++i)
{
std::vector<m2::PointD> points;
serial::LoadOuterPath(src, serial::GeometryCodingParams(), points);
regions.emplace_back(std::move(points));
}
}
bool CountryInfoReader::BelongsToRegion(m2::PointD const & pt, size_t id) const
{
if (!m_countries[id].m_rect.IsPointInside(pt))
return false;
std::vector<m2::RegionD> regions;
LoadRegionsFromDisk(id, regions);
for (auto const & region : regions)
if (region.Contains(pt))
return true;
return false;
}
CountryInfoReader::Info CountryInfoReader::GetMwmInfo(m2::PointD const & pt) const
{
Info info;
info.m_id = GetRegionCountryId(pt);
info.m_name = m_nameGetter(info.m_id);
return info;
}
} // namespace lightweight

View file

@ -0,0 +1,42 @@
#pragma once
#include "storage/country_info_getter.hpp"
#include "storage/country_name_getter.hpp"
#include "storage/storage_defines.hpp"
#include "coding/files_container.hpp"
#include "geometry/point2d.hpp"
#include "geometry/region2d.hpp"
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
namespace lightweight
{
class CountryInfoReader : protected storage::CountryInfoGetterBase
{
public:
struct Info
{
storage::CountryId m_id;
std::string m_name;
};
CountryInfoReader();
/// @note Be careful here, because "lightweight" has no region's geometry cache.
Info GetMwmInfo(m2::PointD const & pt) const;
protected:
void LoadRegionsFromDisk(size_t id, std::vector<m2::RegionD> & regions) const;
// storage::CountryInfoGetterBase overrides:
bool BelongsToRegion(m2::PointD const & pt, size_t id) const override;
private:
std::unique_ptr<FilesContainerR> m_reader;
storage::CountryNameGetter m_nameGetter;
};
} // namespace lightweight

View file

@ -0,0 +1,43 @@
#include "storage/country_name_getter.hpp"
#include "base/assert.hpp"
namespace storage
{
void CountryNameGetter::SetLocale(std::string const & locale)
{
m_getCurLang = platform::GetTextByIdFactory(platform::TextSource::Countries, locale);
}
void CountryNameGetter::SetLocaleForTesting(std::string const & jsonBuffer, std::string const & locale)
{
m_getCurLang = platform::ForTestingGetTextByIdFactory(jsonBuffer, locale);
}
std::string CountryNameGetter::Get(std::string const & key) const
{
ASSERT(!key.empty(), ());
if (m_getCurLang == nullptr)
return std::string();
return (*m_getCurLang)(key);
}
std::string CountryNameGetter::operator()(CountryId const & countryId) const
{
std::string name = Get(countryId);
if (name.empty())
return countryId;
return name;
}
std::string CountryNameGetter::GetLocale() const
{
if (m_getCurLang == nullptr)
return std::string();
return m_getCurLang->GetLocale();
}
} // namespace storage

View file

@ -0,0 +1,42 @@
#pragma once
#include "storage/storage_defines.hpp"
#include "platform/get_text_by_id.hpp"
#include <memory>
#include <string>
namespace storage
{
/// CountryNameGetter is responsible for generating text name for CountryId in a specified locale
/// To get this country name use operator().
/// If the country caption is not available for specified locale, class tries to find it in
/// English locale.
class CountryNameGetter
{
public:
/// @return current locale. For example "en", "ru", "zh-Hant" and so on.
/// \note The method returns correct locale after SetLocale has been called.
/// If not, it returns an empty string.
std::string GetLocale() const;
/// \brief Sets a locale.
/// @param locale is a string representation of locale. For example "en", "ru", "zh-Hant" and so on.
/// \note See countries/languages.txt for the full list of available locales.
void SetLocale(std::string const & locale);
/// \brief Gets localized string for key. If not found returns empty string.
/// @param key is a string for lookup.
/// \note Unlike operator (), does not return key if localized string is not found.
std::string Get(std::string const & key) const;
std::string operator()(CountryId const & countryId) const;
// for testing
void SetLocaleForTesting(std::string const & jsonBuffer, std::string const & locale);
private:
std::unique_ptr<platform::GetTextById> m_getCurLang;
};
} // namespace storage

View file

@ -0,0 +1,17 @@
#include "storage/country_parent_getter.hpp"
namespace storage
{
CountryParentGetter::CountryParentGetter(std::string const & countriesFile, std::string const & countriesDir)
{
if (countriesFile.empty())
m_storage = std::make_shared<Storage>();
else
m_storage = std::make_shared<Storage>(countriesFile, countriesDir);
}
std::string CountryParentGetter::operator()(std::string const & id) const
{
return m_storage->GetParentIdFor(id);
}
} // namespace storage

View file

@ -0,0 +1,21 @@
#pragma once
#include "storage/storage.hpp"
#include <memory>
#include <string>
namespace storage
{
class CountryParentGetter
{
public:
CountryParentGetter(std::string const & countriesFile = "", std::string const & countriesDir = "");
std::string operator()(std::string const & id) const;
Storage const & GetStorageForTesting() const { return *m_storage; }
private:
std::shared_ptr<Storage> m_storage;
};
} // namespace storage

View file

@ -0,0 +1,493 @@
#include "storage/country_tree.hpp"
#include "platform/platform.hpp"
#include "coding/reader.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include "cppjansson/cppjansson.hpp"
#include <algorithm>
namespace storage
{
using namespace std;
// Mwm subtree attributes. They can be calculated based on information contained in countries.txt.
// The first in the pair is number of mwms in a subtree. The second is sum of sizes of
// all mwms in a subtree.
using MwmSubtreeAttrs = pair<MwmCounter, MwmSize>;
namespace
{
class StoreInterface
{
public:
virtual ~StoreInterface() = default;
virtual Country * InsertToCountryTree(CountryId const & id, MwmSize mapSize, string const & mapSha1, size_t depth,
CountryId const & parent) = 0;
virtual void InsertOldMwmMapping(CountryId const & newId, CountryId const & oldId) = 0;
virtual void InsertAffiliation(CountryId const & countryId, string const & affilation) = 0;
virtual void InsertCountryNameSynonym(CountryId const & countryId, string const & synonym) = 0;
virtual void InsertMwmTopCityGeoId(CountryId const & countryId, uint64_t const & geoObjectId) {}
virtual void InsertTopCountryGeoIds(CountryId const & countryId, vector<uint64_t> const & geoObjectIds) {}
virtual OldMwmMapping GetMapping() const = 0;
};
class StoreCountries : public StoreInterface
{
CountryTree & m_countries;
Affiliations & m_affiliations;
CountryNameSynonyms & m_countryNameSynonyms;
MwmTopCityGeoIds & m_mwmTopCityGeoIds;
MwmTopCountryGeoIds & m_mwmTopCountryGeoIds;
OldMwmMapping m_idsMapping;
public:
StoreCountries(CountryTree & countries, Affiliations & affiliations, CountryNameSynonyms & countryNameSynonyms,
MwmTopCityGeoIds & mwmTopCityGeoIds, MwmTopCountryGeoIds & mwmTopCountryGeoIds)
: m_countries(countries)
, m_affiliations(affiliations)
, m_countryNameSynonyms(countryNameSynonyms)
, m_mwmTopCityGeoIds(mwmTopCityGeoIds)
, m_mwmTopCountryGeoIds(mwmTopCountryGeoIds)
{}
~StoreCountries()
{
for (auto & entry : m_affiliations)
base::SortUnique(entry.second);
}
// StoreInterface overrides:
Country * InsertToCountryTree(CountryId const & id, MwmSize mapSize, string const & mapSha1, size_t depth,
CountryId const & parent) override
{
Country country(id, parent);
if (mapSize)
country.SetFile(platform::CountryFile{id, mapSize, mapSha1});
return &m_countries.AddAtDepth(depth, std::move(country));
}
void InsertOldMwmMapping(CountryId const & newId, CountryId const & oldId) override
{
m_idsMapping[oldId].insert(newId);
}
void InsertAffiliation(CountryId const & countryId, string const & affilation) override
{
ASSERT(!affilation.empty(), ());
ASSERT(!countryId.empty(), ());
m_affiliations[affilation].push_back(countryId);
}
void InsertCountryNameSynonym(CountryId const & countryId, string const & synonym) override
{
ASSERT(!synonym.empty(), ());
ASSERT(!countryId.empty(), ());
ASSERT(m_countryNameSynonyms.find(synonym) == m_countryNameSynonyms.end(),
("Synonym must identify CountryTree node where the country is located. Country cannot be "
"located at multiple nodes."));
m_countryNameSynonyms[synonym] = countryId;
}
void InsertMwmTopCityGeoId(CountryId const & countryId, uint64_t const & geoObjectId) override
{
ASSERT(!countryId.empty(), ());
ASSERT_NOT_EQUAL(geoObjectId, 0, ());
base::GeoObjectId id(geoObjectId);
m_mwmTopCityGeoIds.emplace(countryId, std::move(id));
}
void InsertTopCountryGeoIds(CountryId const & countryId, vector<uint64_t> const & geoObjectIds) override
{
ASSERT(!countryId.empty(), ());
ASSERT(!geoObjectIds.empty(), ());
vector<base::GeoObjectId> ids(geoObjectIds.cbegin(), geoObjectIds.cend());
m_mwmTopCountryGeoIds.emplace(countryId, std::move(ids));
}
OldMwmMapping GetMapping() const override { return m_idsMapping; }
};
class StoreFile2Info : public StoreInterface
{
OldMwmMapping m_idsMapping;
map<string, CountryInfo> & m_file2info;
public:
explicit StoreFile2Info(map<string, CountryInfo> & file2info) : m_file2info(file2info) {}
// StoreInterface overrides:
Country * InsertToCountryTree(CountryId const & id, MwmSize /* mapSize */, string const & /* mapSha1 */,
size_t /* depth */, CountryId const & /* parent */) override
{
CountryInfo info(id);
m_file2info[id] = std::move(info);
return nullptr;
}
void InsertOldMwmMapping(CountryId const & /* newId */, CountryId const & /* oldId */) override {}
void InsertAffiliation(CountryId const & /* countryId */, string const & /* affilation */) override {}
void InsertCountryNameSynonym(CountryId const & /* countryId */, string const & /* synonym */) override {}
OldMwmMapping GetMapping() const override
{
ASSERT(false, ());
return map<CountryId, CountriesSet>();
}
};
} // namespace
// CountryTree::Node -------------------------------------------------------------------------------
CountryTree::Node * CountryTree::Node::AddAtDepth(size_t level, Country && value)
{
Node * node = this;
while (--level > 0 && !node->m_children.empty())
node = node->m_children.back().get();
ASSERT_EQUAL(level, 0, ());
return node->Add(std::move(value));
}
CountryTree::Node const & CountryTree::Node::Parent() const
{
CHECK(HasParent(), ());
return *m_parent;
}
CountryTree::Node const & CountryTree::Node::Child(size_t index) const
{
ASSERT_LESS(index, m_children.size(), ());
return *m_children[index];
}
void CountryTree::Node::ForEachChild(CountryTree::Node::NodeCallback const & f)
{
for (auto & child : m_children)
f(*child);
}
void CountryTree::Node::ForEachChild(CountryTree::Node::NodeCallback const & f) const
{
for (auto const & child : m_children)
f(*child);
}
void CountryTree::Node::ForEachDescendant(CountryTree::Node::NodeCallback const & f)
{
for (auto & child : m_children)
{
f(*child);
child->ForEachDescendant(f);
}
}
void CountryTree::Node::ForEachDescendant(CountryTree::Node::NodeCallback const & f) const
{
for (auto const & child : m_children)
{
f(*child);
child->ForEachDescendant(f);
}
}
void CountryTree::Node::ForEachInSubtree(CountryTree::Node::NodeCallback const & f)
{
f(*this);
for (auto & child : m_children)
child->ForEachInSubtree(f);
}
void CountryTree::Node::ForEachInSubtree(CountryTree::Node::NodeCallback const & f) const
{
f(*this);
for (auto const & child : m_children)
child->ForEachInSubtree(f);
}
void CountryTree::Node::ForEachAncestorExceptForTheRoot(CountryTree::Node::NodeCallback const & f)
{
if (m_parent == nullptr || m_parent->m_parent == nullptr)
return;
f(*m_parent);
m_parent->ForEachAncestorExceptForTheRoot(f);
}
void CountryTree::Node::ForEachAncestorExceptForTheRoot(CountryTree::Node::NodeCallback const & f) const
{
if (m_parent == nullptr || m_parent->m_parent == nullptr)
return;
f(*m_parent);
m_parent->ForEachAncestorExceptForTheRoot(f);
}
CountryTree::Node * CountryTree::Node::Add(Country && value)
{
m_children.emplace_back(std::make_unique<Node>(std::move(value), this));
return m_children.back().get();
}
// CountryTree -------------------------------------------------------------------------------------
CountryTree::Node const & CountryTree::GetRoot() const
{
CHECK(m_countryTree, ());
return *m_countryTree;
}
CountryTree::Node & CountryTree::GetRoot()
{
CHECK(m_countryTree, ());
return *m_countryTree;
}
Country & CountryTree::AddAtDepth(size_t level, Country && value)
{
Node * added = nullptr;
if (level == 0)
{
ASSERT(IsEmpty(), ());
m_countryTree = std::make_unique<Node>(std::move(value), nullptr); // Creating the root node.
added = m_countryTree.get();
}
else
{
added = m_countryTree->AddAtDepth(level, std::move(value));
}
ASSERT(added, ());
m_countryTreeMap.insert(make_pair(added->Value().Name(), added));
return added->Value();
}
void CountryTree::Clear()
{
m_countryTree.reset();
m_countryTreeMap.clear();
}
void CountryTree::Find(CountryId const & key, NodesBufferT & found) const
{
found.clear();
if (IsEmpty())
return;
if (key == m_countryTree->Value().Name())
found.push_back(m_countryTree.get());
auto const range = m_countryTreeMap.equal_range(key);
for (auto it = range.first; it != range.second; ++it)
found.push_back(it->second);
}
CountryTree::Node const * CountryTree::FindFirst(CountryId const & key) const
{
if (IsEmpty())
return nullptr;
NodesBufferT found;
Find(key, found);
if (found.empty())
return nullptr;
return found[0];
}
CountryTree::Node const * CountryTree::FindFirstLeaf(CountryId const & key) const
{
if (IsEmpty())
return nullptr;
NodesBufferT found;
Find(key, found);
for (auto node : found)
if (node->ChildrenCount() == 0)
return node;
return nullptr;
}
MwmSubtreeAttrs LoadGroupImpl(size_t depth, json_t * node, CountryId const & parent, StoreInterface & store)
{
CountryId id;
FromJSONObject(node, "id", id);
vector<string> countryNameSynonyms;
FromJSONObjectOptionalField(node, "country_name_synonyms", countryNameSynonyms);
for (auto const & synonym : countryNameSynonyms)
store.InsertCountryNameSynonym(id, synonym);
vector<string> affiliations;
FromJSONObjectOptionalField(node, "affiliations", affiliations);
for (auto const & affilationValue : affiliations)
store.InsertAffiliation(id, affilationValue);
uint64_t geoObjectId = 0;
FromJSONObjectOptionalField(node, "top_city_geo_id", geoObjectId);
if (geoObjectId != 0)
store.InsertMwmTopCityGeoId(id, geoObjectId);
vector<uint64_t> topCountryIds;
FromJSONObjectOptionalField(node, "top_countries_geo_ids", topCountryIds);
if (!topCountryIds.empty())
store.InsertTopCountryGeoIds(id, topCountryIds);
int nodeSize;
FromJSONObjectOptionalField(node, "s", nodeSize);
ASSERT_LESS_OR_EQUAL(0, nodeSize, ());
string nodeHash;
FromJSONObjectOptionalField(node, "sha1_base64", nodeHash);
// We expect that mwm and routing files should be less than 2GB.
Country * addedNode = store.InsertToCountryTree(id, nodeSize, nodeHash, depth, parent);
MwmCounter mwmCounter = 0;
MwmSize mwmSize = 0;
vector<json_t *> children;
FromJSONObjectOptionalField(node, "g", children);
if (children.empty())
{
mwmCounter = 1; // It's a leaf. Any leaf contains one mwm.
mwmSize = nodeSize;
}
else
{
for (json_t * child : children)
{
MwmSubtreeAttrs const childAttr = LoadGroupImpl(depth + 1, child, id, store);
mwmCounter += childAttr.first;
mwmSize += childAttr.second;
}
}
if (addedNode != nullptr)
addedNode->SetSubtreeAttrs(mwmCounter, mwmSize);
return make_pair(mwmCounter, mwmSize);
}
bool LoadCountriesImpl(string const & jsonBuffer, StoreInterface & store)
{
try
{
base::Json root(jsonBuffer.c_str());
LoadGroupImpl(0 /* depth */, root.get(), kInvalidCountryId, store);
return true;
}
catch (base::Json::Exception const & e)
{
LOG(LERROR, (e.Msg()));
return false;
}
}
int64_t LoadCountriesFromBuffer(string const & jsonBuffer, CountryTree & countries, Affiliations & affiliations,
CountryNameSynonyms & countryNameSynonyms, MwmTopCityGeoIds & mwmTopCityGeoIds,
MwmTopCountryGeoIds & mwmTopCountryGeoIds)
{
countries.Clear();
affiliations.clear();
int64_t version = -1;
try
{
base::Json root(jsonBuffer.c_str());
FromJSONObject(root.get(), "v", version);
StoreCountries store(countries, affiliations, countryNameSynonyms, mwmTopCityGeoIds, mwmTopCountryGeoIds);
if (!LoadCountriesImpl(jsonBuffer, store))
return -1;
}
catch (base::Json::Exception const & e)
{
LOG(LWARNING, (e.Msg()));
}
return version;
}
namespace
{
unique_ptr<Reader> GetReaderImpl(Platform & pl, string const & file, string const & scope)
{
try
{
return pl.GetReader(file, scope);
}
catch (RootException const &)
{}
return nullptr;
}
} // namespace
int64_t LoadCountriesFromFile(string const & path, CountryTree & countries, Affiliations & affiliations,
CountryNameSynonyms & countryNameSynonyms, MwmTopCityGeoIds & mwmTopCityGeoIds,
MwmTopCountryGeoIds & mwmTopCountryGeoIds)
{
string json;
int64_t version = -1;
// Choose the latest version from "resource" or "writable":
// w > r in case of autoupdates
// r > w in case of a new countries file with an updated app
auto & pl = GetPlatform();
auto reader = GetReaderImpl(pl, path, "fr");
if (reader)
{
reader->ReadAsString(json);
version = LoadCountriesFromBuffer(json, countries, affiliations, countryNameSynonyms, mwmTopCityGeoIds,
mwmTopCountryGeoIds);
}
reader = GetReaderImpl(pl, path, "w");
if (reader)
{
CountryTree newCountries;
Affiliations newAffs;
CountryNameSynonyms newSyms;
MwmTopCityGeoIds newCityIds;
MwmTopCountryGeoIds newCountryIds;
reader->ReadAsString(json);
int64_t const newVersion = LoadCountriesFromBuffer(json, newCountries, newAffs, newSyms, newCityIds, newCountryIds);
if (newVersion > version)
{
version = newVersion;
countries = std::move(newCountries);
affiliations = std::move(newAffs);
countryNameSynonyms = std::move(newSyms);
mwmTopCityGeoIds = std::move(newCityIds);
mwmTopCountryGeoIds = std::move(newCountryIds);
}
}
return version;
}
void LoadCountryFile2CountryInfo(string const & jsonBuffer, map<string, CountryInfo> & id2info)
{
ASSERT(id2info.empty(), ());
int64_t version = -1;
try
{
base::Json root(jsonBuffer.c_str());
FromJSONObjectOptionalField(root.get(), "v", version);
StoreFile2Info store(id2info);
LoadCountriesImpl(jsonBuffer, store);
}
catch (base::Json::Exception const & e)
{
LOG(LERROR, (e.Msg()));
}
}
} // namespace storage

View file

@ -0,0 +1,130 @@
#pragma once
#include "storage/country.hpp"
#include "storage/storage_defines.hpp"
#include "base/buffer_vector.hpp"
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace storage
{
/// \brief This class is developed for using in Storage. It's a implementation of a tree with
/// ability
/// of access to its nodes in expected constant time with the help of hash table.
/// It should be filled with AddAtDepth method.
/// This class is used in Storage and filled based on countries.txt (countries_migrate.txt).
/// While filling CountryTree nodes in countries.txt should be visited in DFS order.
class CountryTree
{
public:
/// This class is developed for using in CountryTree. It's a implementation of a tree.
/// It should be filled with AddAtDepth method.
/// This class is used in filled based on countries.txt (countries_migrate.txt).
/// While filling Node nodes in countries.txt should be visited in DFS order.
class Node
{
public:
using NodeCallback = std::function<void(Node const &)>;
Node(Country && value, Node * parent) : m_value(std::move(value)), m_parent(parent) {}
Country const & Value() const { return m_value; }
Country & Value() { return m_value; }
/// \brief Adds a node to a tree with root == |this|.
/// \param level is depth where a node with |value| will be add. |level| == 0 means |this|.
/// When the method is called the node |this| exists. So it's wrong call it with |level| == 0.
/// If |level| == 1 the node with |value| will be added as a child of this.
/// If |level| == 2 the node with |value| will be added as a child of the last added child of the |this|.
/// And so on.
/// \param value is a value of node which will be added.
/// \note This method does not let to add a node to an arbitrary place in the tree.
/// It's posible to add children only from "right side".
Node * AddAtDepth(size_t level, Country && value);
bool HasParent() const { return m_parent != nullptr; }
bool IsRoot() const { return !HasParent(); }
Node const & Parent() const;
Node const & Child(size_t index) const;
size_t ChildrenCount() const { return m_children.size(); }
/// \brief Calls |f| for each first generation descendant of the node.
void ForEachChild(NodeCallback const & f);
void ForEachChild(NodeCallback const & f) const;
/// \brief Calls |f| for all nodes (add descendant) in the tree.
void ForEachDescendant(NodeCallback const & f);
void ForEachDescendant(NodeCallback const & f) const;
void ForEachInSubtree(NodeCallback const & f);
void ForEachInSubtree(NodeCallback const & f) const;
void ForEachAncestorExceptForTheRoot(NodeCallback const & f);
void ForEachAncestorExceptForTheRoot(NodeCallback const & f) const;
private:
Node * Add(Country && value);
Country m_value;
/// \brief m_children contains all first generation descendants of the node.
/// Note. Once created the order of elements of |m_children| should not be changed.
/// See implementation of AddAtDepth and Add methods for details.
std::vector<std::unique_ptr<Node>> m_children;
Node * m_parent;
};
bool IsEmpty() const { return m_countryTree == nullptr; }
Node const & GetRoot() const;
Node & GetRoot();
Country & AddAtDepth(size_t level, Country && value);
/// Deletes all children and makes tree empty
void Clear();
using NodesBufferT = buffer_vector<Node const *, 4>;
/// \brief Checks all nodes in tree to find an equal one. If there're several equal nodes
/// returns the first found.
/// \returns a poiter item in the tree if found and nullptr otherwise.
void Find(CountryId const & key, NodesBufferT & found) const;
Node const * FindFirst(CountryId const & key) const;
/// \brief Find only leaves.
/// \note It's a termprary fucntion for compatablity with old countries.txt.
/// When new countries.txt with unique ids will be added FindLeaf will be removed
/// and Find will be used intead.
/// @TODO(bykoianko) Remove this method on countries.txt update.
Node const * FindFirstLeaf(CountryId const & key) const;
private:
std::unique_ptr<Node> m_countryTree;
std::multimap<CountryId, Node *> m_countryTreeMap;
};
/// @return version of country file or -1 if error was encountered
int64_t LoadCountriesFromBuffer(std::string const & buffer, CountryTree & countries, Affiliations & affiliations,
CountryNameSynonyms & countryNameSynonyms, MwmTopCityGeoIds & mwmTopCityGeoIds,
MwmTopCountryGeoIds & mwmTopCountryGeoIds);
int64_t LoadCountriesFromFile(std::string const & path, CountryTree & countries, Affiliations & affiliations,
CountryNameSynonyms & countryNameSynonyms, MwmTopCityGeoIds & mwmTopCityGeoIds,
MwmTopCountryGeoIds & mwmTopCountryGeoIds);
void LoadCountryFile2CountryInfo(std::string const & jsonBuffer, std::map<std::string, CountryInfo> & id2info);
} // namespace storage

View file

@ -0,0 +1,61 @@
#include "storage/country_tree_helpers.hpp"
#include "base/logging.hpp"
#include <utility>
#include <vector>
namespace storage
{
CountryId GetTopmostParentFor(CountryTree const & countries, CountryId const & countryId)
{
CountryTree::NodesBufferT nodes;
countries.Find(countryId, nodes);
if (nodes.empty())
{
LOG(LWARNING, ("CountryId =", countryId, "not found in countries."));
return {};
}
if (nodes.size() > 1)
{
// Disputed territory. Has multiple parents.
CHECK(nodes[0]->HasParent(), ());
auto const parentId = nodes[0]->Parent().Value().Name();
for (size_t i = 1; i < nodes.size(); ++i)
if (nodes[i]->Parent().Value().Name() != parentId)
return countryId;
return GetTopmostParentFor(countries, parentId);
}
auto result = nodes[0];
if (!result->HasParent())
return result->Value().Name();
auto parent = &(result->Parent());
while (parent->HasParent())
{
result = parent;
parent = &(result->Parent());
}
return result->Value().Name();
}
std::optional<CountryTree> LoadCountriesFromFile(std::string const & path)
{
Affiliations affiliations;
CountryNameSynonyms countryNameSynonyms;
MwmTopCityGeoIds mwmTopCityGeoIds;
MwmTopCountryGeoIds mwmTopCountryGeoIds;
CountryTree countries;
auto const res =
LoadCountriesFromFile(path, countries, affiliations, countryNameSynonyms, mwmTopCityGeoIds, mwmTopCountryGeoIds);
if (res == -1)
return {};
return std::optional<CountryTree>(std::move(countries));
}
} // namespace storage

View file

@ -0,0 +1,17 @@
#pragma once
#include "storage/country_tree.hpp"
#include "storage/storage_defines.hpp"
#include <optional>
#include <string>
namespace storage
{
// Loads CountryTree only, without affiliations/synonyms/catalogIds.
std::optional<CountryTree> LoadCountriesFromFile(std::string const & path);
// Returns topmost country id prior root id or |countryId| itself, if it's already a topmost node or
// disputed territory id if |countryId| is a disputed territory or belongs to disputed territory.
CountryId GetTopmostParentFor(CountryTree const & countries, CountryId const & countryId);
} // namespace storage

View file

@ -0,0 +1,68 @@
#include "storage/diff_scheme/apply_diff.hpp"
#include "platform/platform.hpp"
#include "coding/internal/file_data.hpp"
#include "base/assert.hpp"
#include "base/cancellable.hpp"
namespace storage
{
namespace diffs
{
void ApplyDiff(ApplyDiffParams && p, base::Cancellable const & cancellable, OnDiffApplicationFinished const & task)
{
using namespace generator::mwm_diff;
GetPlatform().RunTask(Platform::Thread::File, [p = std::move(p), &cancellable, task]
{
CHECK(p.m_diffFile, ());
CHECK(p.m_oldMwmFile, ());
auto & diffReadyPath = p.m_diffReadyPath;
auto & diffFile = p.m_diffFile;
auto const diffPath = diffFile->GetPath(MapFileType::Diff);
auto result = DiffApplicationResult::Failed;
diffFile->SyncWithDisk();
if (!diffFile->OnDisk(MapFileType::Diff))
{
base::RenameFileX(diffReadyPath, diffPath);
diffFile->SyncWithDisk();
}
auto const isFilePrepared = diffFile->OnDisk(MapFileType::Diff);
if (isFilePrepared)
{
std::string const oldMwmPath = p.m_oldMwmFile->GetPath(MapFileType::Map);
std::string const newMwmPath = diffFile->GetPath(MapFileType::Map);
std::string const diffApplyingInProgressPath = newMwmPath + DIFF_APPLYING_FILE_EXTENSION;
result = generator::mwm_diff::ApplyDiff(oldMwmPath, diffApplyingInProgressPath, diffPath, cancellable);
if (result == DiffApplicationResult::Ok && !base::RenameFileX(diffApplyingInProgressPath, newMwmPath))
result = DiffApplicationResult::Failed;
Platform::RemoveFileIfExists(diffApplyingInProgressPath);
if (result != DiffApplicationResult::Ok)
Platform::RemoveFileIfExists(newMwmPath);
}
switch (result)
{
case DiffApplicationResult::Ok: diffFile->DeleteFromDisk(MapFileType::Diff); break;
case DiffApplicationResult::Cancelled:
// The diff file will be deleted by storage.
// Another way would be to leave it on disk but all consequences
// of interacting with storage are much harder to be taken into account that way.
break;
case DiffApplicationResult::Failed: diffFile->DeleteFromDisk(MapFileType::Diff); break;
}
GetPlatform().RunTask(Platform::Thread::Gui, [task, result]() { task(result); });
});
}
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,29 @@
#pragma once
#include "mwm_diff/diff.hpp"
#include "storage/storage_defines.hpp"
#include <functional>
namespace base
{
class Cancellable;
}
namespace storage
{
namespace diffs
{
struct ApplyDiffParams
{
std::string m_diffReadyPath;
LocalFilePtr m_diffFile;
LocalFilePtr m_oldMwmFile;
};
using OnDiffApplicationFinished = std::function<void(generator::mwm_diff::DiffApplicationResult)>;
void ApplyDiff(ApplyDiffParams && p, base::Cancellable const & cancellable, OnDiffApplicationFinished const & task);
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,148 @@
#include "storage/diff_scheme/diff_scheme_loader.hpp"
#include "platform/http_client.hpp"
#include "platform/platform.hpp"
#include "base/logging.hpp"
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include "cppjansson/cppjansson.hpp"
#include "private.h"
using namespace std;
namespace
{
using namespace storage::diffs;
char constexpr kMaxVersionKey[] = "max_version";
char constexpr kMwmsKey[] = "mwms";
char constexpr kNameKey[] = "name";
char constexpr kSizeKey[] = "size";
char constexpr kVersionKey[] = "version";
auto constexpr kTimeoutInSeconds = 5.0;
string SerializeCheckerData(LocalMapsInfo const & info)
{
auto mwmsArrayNode = base::NewJSONArray();
for (auto const & nameAndVersion : info.m_localMaps)
{
auto node = base::NewJSONObject();
ToJSONObject(*node, kNameKey, nameAndVersion.first);
ToJSONObject(*node, kVersionKey, nameAndVersion.second);
json_array_append_new(mwmsArrayNode.get(), node.release());
}
auto const root = base::NewJSONObject();
json_object_set_new(root.get(), kMwmsKey, mwmsArrayNode.release());
ToJSONObject(*root, kMaxVersionKey, info.m_currentDataVersion);
unique_ptr<char, JSONFreeDeleter> buffer(json_dumps(root.get(), JSON_COMPACT));
return buffer.get();
}
NameDiffInfoMap DeserializeResponse(string const & response, LocalMapsInfo::NameVersionMap const & nameVersionMap)
{
if (response.empty())
{
LOG(LERROR, ("Diff response shouldn't be empty."));
return {};
}
base::Json const json(response.c_str());
if (json.get() == nullptr)
return {};
auto const root = json_object_get(json.get(), kMwmsKey);
if (root == nullptr || !json_is_array(root))
return {};
auto const count = json_array_size(root);
if (count == 0 || count != nameVersionMap.size())
{
LOG(LERROR, ("Diff list size in response must be equal to mwm list size in request."));
return {};
}
NameDiffInfoMap diffs;
for (size_t i = 0; i < count; ++i)
{
auto const node = json_array_get(root, i);
if (!node)
{
LOG(LERROR, ("Incorrect server response."));
return {};
}
string name;
FromJSONObject(node, kNameKey, name);
int64_t size;
FromJSONObject(node, kSizeKey, size);
// Invalid size. The diff is not available.
if (size < 0)
continue;
if (nameVersionMap.find(name) == nameVersionMap.end())
{
LOG(LERROR, ("Incorrect country name in response:", name));
return {};
}
DiffInfo info(size, nameVersionMap.at(name));
diffs.emplace(std::move(name), std::move(info));
}
return diffs;
}
NameDiffInfoMap Load(LocalMapsInfo const & info)
{
if (info.m_localMaps.empty() || DIFF_LIST_URL[0] == 0)
return {};
platform::HttpClient request(DIFF_LIST_URL);
string const body = SerializeCheckerData(info);
ASSERT(!body.empty(), ());
request.SetBodyData(body, "application/json");
request.SetTimeout(kTimeoutInSeconds);
NameDiffInfoMap diffs;
if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200)
{
diffs = DeserializeResponse(request.ServerResponse(), info.m_localMaps);
}
else
{
ostringstream ost;
ost << "Request to diffs server failed. Code = " << request.ErrorCode()
<< ", redirection = " << request.WasRedirected();
LOG(LINFO, (ost.str()));
}
return diffs;
}
} // namespace
namespace storage
{
namespace diffs
{
// static
void Loader::Load(LocalMapsInfo && info, DiffsReceivedCallback && callback)
{
GetPlatform().RunTask(Platform::Thread::Network, [info = std::move(info), callback = std::move(callback)]()
{
auto result = ::Load(info);
GetPlatform().RunTask(Platform::Thread::Gui, [result = std::move(result), callback = std::move(callback)]() mutable
{ callback(std::move(result)); });
});
}
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,30 @@
#pragma once
#include "storage/diff_scheme/diff_types.hpp"
#include "storage/storage_defines.hpp"
#include <cstdint>
#include <functional>
#include <unordered_map>
namespace storage
{
namespace diffs
{
struct LocalMapsInfo final
{
using NameVersionMap = std::unordered_map<storage::CountryId, uint64_t>;
uint64_t m_currentDataVersion = 0;
NameVersionMap m_localMaps;
};
using DiffsReceivedCallback = std::function<void(diffs::NameDiffInfoMap && diffs)>;
class Loader final
{
public:
static void Load(LocalMapsInfo && info, DiffsReceivedCallback && callback);
};
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,31 @@
#pragma once
#include "storage/storage_defines.hpp"
#include <cstdint>
#include <string>
#include <unordered_map>
namespace storage
{
namespace diffs
{
// Status of the diffs data source as a whole.
enum class Status
{
NotAvailable,
Available
};
struct DiffInfo final
{
DiffInfo(uint64_t size, uint64_t version) : m_size(size), m_version(version) {}
uint64_t m_size;
uint64_t m_version;
bool m_isApplied = false;
};
using NameDiffInfoMap = std::unordered_map<storage::CountryId, DiffInfo>;
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,107 @@
#include "storage/diff_scheme/diffs_data_source.hpp"
#include <algorithm>
namespace
{
bool IsDiffsAvailable(storage::diffs::NameDiffInfoMap const & diffs)
{
return std::any_of(diffs.cbegin(), diffs.cend(), [](auto const & d) { return d.second.m_isApplied == false; });
}
} // namespace
namespace storage
{
namespace diffs
{
void DiffsDataSource::SetDiffInfo(NameDiffInfoMap && info)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
if (info.empty())
{
m_status = Status::NotAvailable;
}
else
{
m_diffs = std::move(info);
m_status = Status::Available;
}
}
Status DiffsDataSource::GetStatus() const
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
return m_status;
}
bool DiffsDataSource::SizeFor(storage::CountryId const & countryId, uint64_t & size) const
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
if (m_status != Status::Available)
return false;
auto const it = m_diffs.find(countryId);
if (it == m_diffs.cend())
return false;
size = it->second.m_size;
return true;
}
bool DiffsDataSource::SizeToDownloadFor(storage::CountryId const & countryId, uint64_t & size) const
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
return WithNotAppliedDiff(countryId, [&size](DiffInfo const & info) { size = info.m_size; });
}
bool DiffsDataSource::VersionFor(storage::CountryId const & countryId, uint64_t & v) const
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
return WithNotAppliedDiff(countryId, [&v](DiffInfo const & info) { v = info.m_version; });
}
bool DiffsDataSource::HasDiffFor(storage::CountryId const & countryId) const
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
return WithNotAppliedDiff(countryId, [](DiffInfo const &) {});
}
void DiffsDataSource::MarkAsApplied(storage::CountryId const & countryId)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
auto it = m_diffs.find(countryId);
if (it == m_diffs.end())
return;
it->second.m_isApplied = true;
if (!IsDiffsAvailable(m_diffs))
m_status = Status::NotAvailable;
}
void DiffsDataSource::RemoveDiffForCountry(storage::CountryId const & countryId)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
m_diffs.erase(countryId);
if (m_diffs.empty() || !IsDiffsAvailable(m_diffs))
m_status = Status::NotAvailable;
}
void DiffsDataSource::AbortDiffScheme()
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
m_status = Status::NotAvailable;
m_diffs.clear();
}
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,65 @@
#pragma once
#include "storage/diff_scheme/diff_types.hpp"
#include "storage/storage_defines.hpp"
#include "base/thread_checker.hpp"
#include <memory>
namespace storage
{
namespace diffs
{
class DiffsDataSource;
using DiffsSourcePtr = std::shared_ptr<diffs::DiffsDataSource>;
class DiffsDataSource final
{
public:
void SetDiffInfo(NameDiffInfoMap && info);
// If the diff is available, sets |size| to its size and returns true.
// Otherwise, returns false.
bool SizeFor(storage::CountryId const & countryId, uint64_t & size) const;
// Sets |size| to how many bytes are left for the diff to be downloaded for |countryId|
// or 0 if there is no diff available or it has already been downloaded.
// This method may overestimate because it does not account for the possibility
// of resuming an old download, i.e. the return value is either 0 or the diff size.
// Returns true iff the diff is available.
bool SizeToDownloadFor(storage::CountryId const & countryId, uint64_t & size) const;
bool VersionFor(storage::CountryId const & countryId, uint64_t & version) const;
// Checks whether the diff for |countryId| is available for download or
// has been downloaded.
bool HasDiffFor(storage::CountryId const & countryId) const;
void MarkAsApplied(storage::CountryId const & countryId);
void RemoveDiffForCountry(storage::CountryId const & countryId);
void AbortDiffScheme();
Status GetStatus() const;
private:
template <typename Fn>
bool WithNotAppliedDiff(storage::CountryId const & countryId, Fn && fn) const
{
if (m_status != Status::Available)
return false;
auto const it = m_diffs.find(countryId);
if (it == m_diffs.cend() || it->second.m_isApplied)
return false;
fn(it->second);
return true;
}
ThreadChecker m_threadChecker;
Status m_status = Status::NotAvailable;
NameDiffInfoMap m_diffs;
};
} // namespace diffs
} // namespace storage

View file

@ -0,0 +1,10 @@
#pragma once
#include <memory>
namespace storage
{
class MapFilesDownloader;
std::unique_ptr<MapFilesDownloader> GetDownloader();
} // namespace storage

View file

@ -0,0 +1,21 @@
#pragma once
#include "storage/queued_country.hpp"
#include "storage/storage_defines.hpp"
namespace storage
{
class QueueInterface
{
public:
using ForEachCountryFunction = std::function<void(QueuedCountry const & country)>;
virtual bool IsEmpty() const = 0;
virtual size_t Count() const = 0;
virtual bool Contains(CountryId const & country) const = 0;
virtual void ForEachCountry(ForEachCountryFunction const & fn) const = 0;
protected:
virtual ~QueueInterface() = default;
};
} // namespace storage

View file

@ -0,0 +1,72 @@
#include "storage/downloader_queue_universal.hpp"
#include <algorithm>
namespace storage
{
bool Queue::IsEmpty() const
{
return m_queue.empty();
}
size_t Queue::Count() const
{
return m_queue.size();
}
bool Queue::Contains(CountryId const & country) const
{
return base::IsExist(m_queue, country);
}
void Queue::ForEachCountry(ForEachCountryFunction const & fn) const
{
for (auto const & queuedCountry : m_queue)
fn(queuedCountry);
}
void Queue::ForEachCountry(ForEachCountryMutable const & fn)
{
for (auto & queuedCountry : m_queue)
fn(queuedCountry);
}
CountryId const & Queue::GetFirstId() const
{
CHECK(!m_queue.empty(), ());
return m_queue.front().GetCountryId();
}
QueuedCountry const & Queue::GetFirstCountry() const
{
CHECK(!m_queue.empty(), ());
return m_queue.front();
}
void Queue::Remove(storage::CountryId const & id)
{
auto it = std::find(m_queue.begin(), m_queue.end(), id);
if (it != m_queue.end())
m_queue.erase(it);
}
void Queue::PopFront()
{
CHECK(!m_queue.empty(), ());
m_queue.pop_front();
}
void Queue::Append(QueuedCountry && country)
{
m_queue.emplace_back(std::move(country));
m_queue.back().OnCountryInQueue();
}
void Queue::Clear()
{
m_queue.clear();
}
} // namespace storage

View file

@ -0,0 +1,37 @@
#pragma once
#include "storage/downloader_queue_interface.hpp"
#include "storage/queued_country.hpp"
#include "storage/storage_defines.hpp"
#include <list>
#include <utility>
namespace storage
{
class Queue : public QueueInterface
{
public:
using ForEachCountryMutable = std::function<void(QueuedCountry & country)>;
// QueueInterface overrides:
bool IsEmpty() const override;
size_t Count() const override;
bool Contains(CountryId const & country) const override;
void ForEachCountry(ForEachCountryFunction const & fn) const override;
void ForEachCountry(ForEachCountryMutable const & fn);
CountryId const & GetFirstId() const;
QueuedCountry const & GetFirstCountry() const;
void PopFront();
void Append(QueuedCountry && country);
void Remove(CountryId const & country);
void Clear();
private:
std::list<QueuedCountry> m_queue;
};
} // namespace storage

View file

@ -0,0 +1,57 @@
#pragma once
#include "storage/storage_defines.hpp"
#include <functional>
#include <string>
#include <vector>
namespace storage
{
struct DownloaderSearchResult
{
DownloaderSearchResult(CountryId const & countryId, std::string const & matchedName)
: m_countryId(countryId)
, m_matchedName(matchedName)
{}
bool operator==(DownloaderSearchResult const & rhs) const
{
return m_countryId == rhs.m_countryId && m_matchedName == rhs.m_matchedName;
}
bool operator<(DownloaderSearchResult const & rhs) const
{
if (m_countryId != rhs.m_countryId)
return m_countryId < rhs.m_countryId;
return m_matchedName < rhs.m_matchedName;
}
CountryId m_countryId;
std::string m_matchedName;
};
struct DownloaderSearchResults
{
DownloaderSearchResults() : m_endMarker(false) {}
std::vector<DownloaderSearchResult> m_results;
std::string m_query;
// |m_endMarker| is true iff it's the last call of OnResults callback for the search.
bool m_endMarker;
};
struct DownloaderSearchParams
{
std::string m_query;
std::string m_inputLocale;
using OnResults = std::function<void(DownloaderSearchResults)>;
OnResults m_onResults;
};
inline std::string DebugPrint(DownloaderSearchResult const & r)
{
return "(" + r.m_countryId + " " + r.m_matchedName + ")";
}
} // namespace storage

View file

@ -0,0 +1,45 @@
#include "storage/downloading_policy.hpp"
#include "platform/platform.hpp"
using namespace std::chrono;
void StorageDownloadingPolicy::EnableCellularDownload(bool enabled)
{
m_cellularDownloadEnabled = enabled;
m_disableCellularTime = steady_clock::now() + hours(1);
}
bool StorageDownloadingPolicy::IsCellularDownloadEnabled()
{
if (m_cellularDownloadEnabled && steady_clock::now() > m_disableCellularTime)
m_cellularDownloadEnabled = false;
return m_cellularDownloadEnabled;
}
bool StorageDownloadingPolicy::IsDownloadingAllowed()
{
return !(GetPlatform().ConnectionStatus() == Platform::EConnectionType::CONNECTION_WWAN &&
!IsCellularDownloadEnabled());
}
void StorageDownloadingPolicy::ScheduleRetry(storage::CountriesSet const & failedCountries, TProcessFunc const & func)
{
if (IsDownloadingAllowed() && !failedCountries.empty() && m_autoRetryCounter > 0)
{
m_downloadRetryFailed = false;
auto action = [this, func, failedCountries]
{
--m_autoRetryCounter;
func(failedCountries);
};
m_autoRetryWorker.RestartWith([action] { GetPlatform().RunTask(Platform::Thread::Gui, action); });
}
else
{
if (!failedCountries.empty())
m_downloadRetryFailed = true;
m_autoRetryCounter = kAutoRetryCounterMax;
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "storage/storage_defines.hpp"
#include "base/deferred_task.hpp"
#include <chrono>
#include <cstddef>
#include <utility>
class DownloadingPolicy
{
public:
using TProcessFunc = std::function<void(storage::CountriesSet const &)>;
virtual ~DownloadingPolicy() = default;
virtual bool IsDownloadingAllowed() { return true; }
virtual void ScheduleRetry(storage::CountriesSet const &, TProcessFunc const &) {}
};
class StorageDownloadingPolicy : public DownloadingPolicy
{
bool m_cellularDownloadEnabled = false;
bool m_downloadRetryFailed = false;
static size_t constexpr kAutoRetryCounterMax = 3;
size_t m_autoRetryCounter = kAutoRetryCounterMax;
base::DeferredTask m_autoRetryWorker;
std::chrono::time_point<std::chrono::steady_clock> m_disableCellularTime;
public:
StorageDownloadingPolicy() : m_autoRetryWorker(std::chrono::seconds(20)) {}
void EnableCellularDownload(bool enabled);
bool IsCellularDownloadEnabled();
inline bool IsAutoRetryDownloadFailed() const { return m_downloadRetryFailed || m_autoRetryCounter == 0; }
// DownloadingPolicy overrides:
bool IsDownloadingAllowed() override;
void ScheduleRetry(storage::CountriesSet const & failedCountries, TProcessFunc const & func) override;
};

View file

@ -0,0 +1,150 @@
#include "storage/http_map_files_downloader.hpp"
#include "storage/downloader.hpp"
#include "platform/downloader_defines.hpp"
#include "platform/servers_list.hpp"
#include "base/assert.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <functional>
using namespace std::placeholders;
namespace
{
class ErrorHttpRequest : public downloader::HttpRequest
{
public:
explicit ErrorHttpRequest(std::string const & filePath) : HttpRequest(Callback(), Callback()), m_filePath(filePath)
{
m_status = downloader::DownloadStatus::Failed;
}
virtual std::string const & GetData() const { return m_filePath; }
private:
std::string m_filePath;
};
} // anonymous namespace
namespace storage
{
HttpMapFilesDownloader::~HttpMapFilesDownloader()
{
CHECK_THREAD_CHECKER(m_checker, ());
}
void HttpMapFilesDownloader::Download(QueuedCountry && queuedCountry)
{
CHECK_THREAD_CHECKER(m_checker, ());
m_queue.Append(std::move(queuedCountry));
if (m_queue.Count() == 1)
Download();
}
void HttpMapFilesDownloader::Download()
{
CHECK_THREAD_CHECKER(m_checker, ());
auto const & queuedCountry = m_queue.GetFirstCountry();
auto const urls = MakeUrlList(queuedCountry.GetRelativeUrl());
auto const path = queuedCountry.GetFileDownloadPath();
auto const size = queuedCountry.GetDownloadSize();
m_request.reset();
if (IsDownloadingAllowed())
{
queuedCountry.OnStartDownloading();
m_request.reset(downloader::HttpRequest::GetFile(
urls, path, size, std::bind(&HttpMapFilesDownloader::OnMapFileDownloaded, this, queuedCountry, _1),
std::bind(&HttpMapFilesDownloader::OnMapFileDownloadingProgress, this, queuedCountry, _1)));
}
else
{
ErrorHttpRequest error(path);
auto const copy = queuedCountry;
OnMapFileDownloaded(copy, error);
}
}
void HttpMapFilesDownloader::Remove(CountryId const & id)
{
CHECK_THREAD_CHECKER(m_checker, ());
MapFilesDownloader::Remove(id);
if (!m_queue.Contains(id))
return;
if (m_request && m_queue.GetFirstId() == id)
m_request.reset();
m_queue.Remove(id);
if (!m_queue.IsEmpty() && !m_request)
Download();
}
void HttpMapFilesDownloader::Clear()
{
CHECK_THREAD_CHECKER(m_checker, ());
MapFilesDownloader::Clear();
m_request.reset();
m_queue.Clear();
}
QueueInterface const & HttpMapFilesDownloader::GetQueue() const
{
CHECK_THREAD_CHECKER(m_checker, ());
if (m_queue.IsEmpty())
return MapFilesDownloader::GetQueue();
return m_queue;
}
void HttpMapFilesDownloader::OnMapFileDownloaded(QueuedCountry const & queuedCountry, downloader::HttpRequest & request)
{
CHECK_THREAD_CHECKER(m_checker, ());
// Because this method is called deferred on original thread,
// it is possible the country is already removed from queue.
if (m_queue.IsEmpty() || m_queue.GetFirstId() != queuedCountry.GetCountryId())
return;
m_queue.PopFront();
queuedCountry.OnDownloadFinished(request.GetStatus());
m_request.reset();
if (!m_queue.IsEmpty())
Download();
}
void HttpMapFilesDownloader::OnMapFileDownloadingProgress(QueuedCountry const & queuedCountry,
downloader::HttpRequest & request)
{
CHECK_THREAD_CHECKER(m_checker, ());
// Because of this method calls deferred on original thread,
// it is possible the country is already removed from queue.
if (m_queue.IsEmpty() || m_queue.GetFirstId() != queuedCountry.GetCountryId())
return;
queuedCountry.OnDownloadProgress(request.GetProgress());
}
std::unique_ptr<MapFilesDownloader> GetDownloader()
{
return std::make_unique<HttpMapFilesDownloader>();
}
} // namespace storage

View file

@ -0,0 +1,45 @@
#pragma once
#include "storage/downloader_queue_universal.hpp"
#include "storage/map_files_downloader_with_ping.hpp"
#include "platform/http_request.hpp"
#include "base/thread_checker.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace storage
{
/// This class encapsulates HTTP requests for receiving server lists
/// and file downloading.
//
// *NOTE*, this class is not thread-safe.
class HttpMapFilesDownloader : public MapFilesDownloader
{
public:
virtual ~HttpMapFilesDownloader();
// MapFilesDownloader overrides:
void Remove(CountryId const & id) override;
void Clear() override;
QueueInterface const & GetQueue() const override;
private:
// MapFilesDownloader overrides:
void Download(QueuedCountry && queuedCountry) override;
void Download();
void OnMapFileDownloaded(QueuedCountry const & queuedCountry, downloader::HttpRequest & request);
void OnMapFileDownloadingProgress(QueuedCountry const & queuedCountry, downloader::HttpRequest & request);
std::unique_ptr<downloader::HttpRequest> m_request;
Queue m_queue;
DECLARE_THREAD_CHECKER(m_checker);
};
} // namespace storage

View file

@ -0,0 +1,193 @@
#include "storage/map_files_downloader.hpp"
#include "storage/queued_country.hpp"
#include "platform/downloader_utils.hpp"
#include "platform/http_client.hpp"
#include "platform/locale.hpp"
#include "platform/platform.hpp"
#include "platform/servers_list.hpp"
#include "platform/settings.hpp"
#include "coding/url.hpp"
#include "base/assert.hpp"
#include "base/string_utils.hpp"
namespace storage
{
void MapFilesDownloader::DownloadMapFile(QueuedCountry && queuedCountry)
{
if (!m_serversList.empty())
{
Download(std::move(queuedCountry));
return;
}
m_pendingRequests.Append(std::move(queuedCountry));
if (!m_isMetaConfigRequested)
{
RunMetaConfigAsync([this]()
{
m_pendingRequests.ForEachCountry([this](QueuedCountry & country) { Download(std::move(country)); });
m_pendingRequests.Clear();
});
}
}
void MapFilesDownloader::RunMetaConfigAsync(std::function<void()> && callback)
{
m_isMetaConfigRequested = true;
GetPlatform().RunTask(Platform::Thread::Network, [this, callback = std::move(callback)]()
{
GetMetaConfig([this, callback = std::move(callback)](MetaConfig const & metaConfig)
{
m_serversList = metaConfig.m_serversList;
settings::Update(metaConfig.m_settings);
callback();
// Reset flag to invoke servers list downloading next time if current request has failed.
m_isMetaConfigRequested = false;
});
});
}
void MapFilesDownloader::Remove(CountryId const & id)
{
if (!m_pendingRequests.IsEmpty())
m_pendingRequests.Remove(id);
}
void MapFilesDownloader::Clear()
{
m_pendingRequests.Clear();
}
QueueInterface const & MapFilesDownloader::GetQueue() const
{
return m_pendingRequests;
}
void MapFilesDownloader::DownloadAsString(std::string url, std::function<bool(std::string const &)> && callback,
bool forceReset /* = false */)
{
EnsureMetaConfigReady([this, forceReset, url = std::move(url), callback = std::move(callback)]()
{
if ((m_fileRequest && !forceReset) || m_serversList.empty())
return;
// Servers are sorted from best to worst.
m_fileRequest.reset(RequestT::Get(url::Join(m_serversList.front(), url),
[this, callback = std::move(callback)](RequestT & request)
{
bool deleteRequest = true;
auto const & buffer = request.GetData();
if (!buffer.empty())
{
// Update deleteRequest flag if new download was requested in callback.
deleteRequest = !callback(buffer);
}
if (deleteRequest)
m_fileRequest.reset();
}));
});
}
void MapFilesDownloader::EnsureMetaConfigReady(std::function<void()> && callback)
{
/// @todo Implement logic if m_metaConfig is "outdated".
/// Fetch new servers list on each download request?
if (!m_serversList.empty())
{
callback();
}
else if (!m_isMetaConfigRequested)
{
RunMetaConfigAsync(std::move(callback));
}
else
{
// skip this request without callback call
}
}
std::vector<std::string> MapFilesDownloader::MakeUrlListLegacy(std::string const & fileName) const
{
return MakeUrlList(downloader::GetFileDownloadUrl(fileName, m_dataVersion));
}
void MapFilesDownloader::SetServersList(ServersList const & serversList)
{
m_serversList = serversList;
}
void MapFilesDownloader::SetDownloadingPolicy(DownloadingPolicy * policy)
{
m_downloadingPolicy = policy;
}
bool MapFilesDownloader::IsDownloadingAllowed() const
{
return m_downloadingPolicy == nullptr || m_downloadingPolicy->IsDownloadingAllowed();
}
std::vector<std::string> MapFilesDownloader::MakeUrlList(std::string const & relativeUrl) const
{
std::vector<std::string> urls;
urls.reserve(m_serversList.size());
for (auto const & server : m_serversList)
urls.emplace_back(url::Join(server, relativeUrl));
return urls;
}
std::string GetAcceptLanguage()
{
auto const locale = platform::GetCurrentLocale();
return locale.m_language + "-" + locale.m_country;
}
// static
MetaConfig MapFilesDownloader::LoadMetaConfig()
{
Platform & pl = GetPlatform();
std::string const metaServerUrl = pl.MetaServerUrl();
std::string httpResult;
if (!metaServerUrl.empty())
{
LOG(LINFO, ("Requesting metaserver", metaServerUrl));
platform::HttpClient request(metaServerUrl);
request.SetRawHeader("X-OM-DataVersion", std::to_string(m_dataVersion));
request.SetRawHeader("X-OM-AppVersion", pl.Version());
request.SetRawHeader("Accept-Language", GetAcceptLanguage());
request.SetTimeout(10.0); // timeout in seconds
request.RunHttpRequest(httpResult);
}
std::optional<MetaConfig> metaConfig = downloader::ParseMetaConfig(httpResult);
if (!metaConfig)
{
metaConfig = downloader::ParseMetaConfig(pl.DefaultUrlsJSON());
CHECK(metaConfig, ());
LOG(LWARNING, ("Can't get metaserver configuration, using default servers:", metaConfig->m_serversList));
}
else
{
LOG(LINFO, ("Got servers list:", metaConfig->m_serversList));
}
CHECK(!metaConfig->m_serversList.empty(), ());
return *metaConfig;
}
void MapFilesDownloader::GetMetaConfig(MetaConfigCallback const & callback)
{
callback(LoadMetaConfig());
}
} // namespace storage

View file

@ -0,0 +1,113 @@
#pragma once
#include "storage/downloader_queue_universal.hpp"
#include "storage/downloading_policy.hpp"
#include "storage/queued_country.hpp"
#include "platform/downloader_defines.hpp"
#include "platform/http_request.hpp"
#include "platform/safe_callback.hpp"
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "platform/servers_list.hpp"
namespace storage
{
using downloader::MetaConfig;
// This interface encapsulates HTTP routines for receiving servers
// URLs and downloading a single map file.
class MapFilesDownloader
{
public:
// Denotes bytes downloaded and total number of bytes.
using ServersList = std::vector<std::string>;
using MetaConfigCallback = platform::SafeCallback<void(MetaConfig const & metaConfig)>;
virtual ~MapFilesDownloader() = default;
/// Asynchronously downloads a map file (first queries the metaserver
/// for the map servers list, if it haven't been done before),
/// periodically invokes onProgress callback and finally invokes onDownloaded
/// callback. Both callbacks will be invoked on the main thread.
void DownloadMapFile(QueuedCountry && queuedCountry);
// Removes item from m_quarantine queue when list of servers is not received.
// Parent method must be called into override method.
virtual void Remove(CountryId const & id);
// Clears m_quarantine queue when list of servers is not received.
// Parent method must be called into override method.
virtual void Clear();
// Returns m_quarantine queue when list of servers is not received.
// Parent method must be called into override method.
virtual QueueInterface const & GetQueue() const;
/**
* @brief Async file download as string buffer (for small files only).
* Request can be skipped if current servers list is empty.
* Callback will be skipped on download error.
* NOTE: not in use at the moment.
* @param[in] url Final url part like "index.json" or "maps/210415/countries.txt".
* @param[in] forceReset True - force reset current request, if any.
*/
void DownloadAsString(std::string url, std::function<bool(std::string const &)> && callback, bool forceReset = false);
// Used in tests only.
void SetServersList(ServersList const & serversList);
void SetDownloadingPolicy(DownloadingPolicy * policy);
void SetDataVersion(int64_t version) { m_dataVersion = version; }
/// @name Legacy functions for Android resources downloading routine (initial World download).
/// @{
void EnsureMetaConfigReady(std::function<void()> && callback);
std::vector<std::string> MakeUrlListLegacy(std::string const & fileName) const;
/// @}
protected:
bool IsDownloadingAllowed() const;
// Produces download urls for all servers.
std::vector<std::string> MakeUrlList(std::string const & relativeUrl) const;
// Synchronously loads a list of map servers from the metaserver.
// On dl failure fallbacks to the hardcoded list.
MetaConfig LoadMetaConfig();
private:
/**
* @brief This method is blocking and should be called on network thread.
* Default implementation receives a list of all servers that can be asked
* for a map file and invokes callback on the main thread (@see MetaConfigCallback as SafeCallback).
*/
virtual void GetMetaConfig(MetaConfigCallback const & callback);
/// Asynchronously downloads the file and saves result to provided directory.
virtual void Download(QueuedCountry && queuedCountry) = 0;
/// @param[in] callback Called in main thread (@see GetMetaConfig).
void RunMetaConfigAsync(std::function<void()> && callback);
/// Current file downloading request for DownloadAsString.
using RequestT = downloader::HttpRequest;
std::unique_ptr<RequestT> m_fileRequest;
ServersList m_serversList;
int64_t m_dataVersion = 0;
/// Used as guard for m_serversList assign.
std::atomic_bool m_isMetaConfigRequested = false;
DownloadingPolicy * m_downloadingPolicy = nullptr;
// This queue accumulates download requests before
// the servers list is received on the network thread.
Queue m_pendingRequests;
};
} // namespace storage

View file

@ -0,0 +1,27 @@
#include "storage/map_files_downloader_with_ping.hpp"
#include "storage/pinger.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
namespace storage
{
void MapFilesDownloaderWithPing::GetMetaConfig(MetaConfigCallback const & callback)
{
ASSERT(callback, ());
MetaConfig metaConfig = LoadMetaConfig();
CHECK(!metaConfig.m_serversList.empty(), ());
// Sort the list of servers by latency.
/// @todo(pastk:: actually the sort order is used only in MapFilesDownloader::DownloadAsString()
/// to get e.g. countries.txt but this code is not enabled at the moment.
auto const sorted = Pinger::ExcludeUnavailableAndSortEndpoints(metaConfig.m_serversList);
// Keep the original list if all servers are unavailable.
if (!sorted.empty())
metaConfig.m_serversList = sorted;
callback(metaConfig);
}
} // namespace storage

View file

@ -0,0 +1,13 @@
#pragma once
#include "storage/map_files_downloader.hpp"
namespace storage
{
class MapFilesDownloaderWithPing : public MapFilesDownloader
{
public:
// MapFilesDownloader overrides:
void GetMetaConfig(MetaConfigCallback const & callback) override;
};
} // namespace storage

70
libs/storage/pinger.cpp Normal file
View file

@ -0,0 +1,70 @@
#include "storage/pinger.hpp"
#include "platform/http_client.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/thread_pool_delayed.hpp"
#include <chrono>
namespace pinger
{
auto constexpr kTimeoutInSeconds = 4.0;
int64_t constexpr kInvalidPing = -1;
int64_t DoPing(std::string const & url)
{
using namespace std::chrono;
if (url.empty())
{
ASSERT(false, ("Metaserver returned an empty url."));
return kInvalidPing;
}
LOG(LDEBUG, ("Pinging server", url));
platform::HttpClient request(url);
request.SetHttpMethod("HEAD");
request.SetTimeout(kTimeoutInSeconds);
auto const begin = high_resolution_clock::now();
if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200)
{
return duration_cast<milliseconds>(high_resolution_clock::now() - begin).count();
}
else
{
LOG(LWARNING, ("Ping to server", url, "failed with code =", request.ErrorCode(),
"; wasRedirected =", request.WasRedirected()));
}
return kInvalidPing;
}
} // namespace pinger
namespace storage
{
// static
Pinger::Endpoints Pinger::ExcludeUnavailableAndSortEndpoints(Endpoints const & urls)
{
auto const size = urls.size();
CHECK_GREATER(size, 0, ());
using EntryT = std::pair<int64_t, size_t>;
std::vector<EntryT> timeUrls(size, {pinger::kInvalidPing, 0});
{
base::DelayedThreadPool pool(size, base::DelayedThreadPool::Exit::ExecPending);
for (size_t i = 0; i < size; ++i)
pool.Push([&urls, &timeUrls, i] { timeUrls[i] = {pinger::DoPing(urls[i]), i}; });
}
std::sort(timeUrls.begin(), timeUrls.end(), [](EntryT const & e1, EntryT const & e2) { return e1.first < e2.first; });
Endpoints readyUrls;
for (auto const & [ping, index] : timeUrls)
if (ping >= 0)
readyUrls.push_back(urls[index]);
return readyUrls;
}
} // namespace storage

17
libs/storage/pinger.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "platform/safe_callback.hpp"
#include <string>
#include <vector>
namespace storage
{
class Pinger
{
public:
using Endpoints = std::vector<std::string>;
// Pings all endpoints and a returns latency-sorted list of available ones. Works synchronously.
static Endpoints ExcludeUnavailableAndSortEndpoints(Endpoints const & urls);
};
} // namespace storage

View file

@ -0,0 +1,107 @@
#include "storage/queued_country.hpp"
#include "storage/storage_helpers.hpp"
#include "platform/downloader_utils.hpp"
#include "platform/local_country_file_utils.hpp"
#include "base/assert.hpp"
namespace storage
{
QueuedCountry::QueuedCountry(platform::CountryFile const & countryFile, CountryId const & countryId, MapFileType type,
int64_t currentDataVersion, std::string const & dataDir,
diffs::DiffsSourcePtr const & diffs)
: m_countryFile(countryFile)
, m_countryId(countryId)
, m_fileType(type)
, m_currentDataVersion(currentDataVersion)
, m_dataDir(dataDir)
, m_diffsDataSource(diffs)
{
ASSERT(IsCountryIdValid(GetCountryId()), ("Only valid countries may be downloaded."));
ASSERT(m_diffsDataSource != nullptr, ());
}
void QueuedCountry::Subscribe(Subscriber & subscriber)
{
m_subscriber = &subscriber;
}
void QueuedCountry::Unsubscribe()
{
m_subscriber = nullptr;
}
void QueuedCountry::SetFileType(MapFileType type)
{
m_fileType = type;
}
MapFileType QueuedCountry::GetFileType() const
{
return m_fileType;
}
CountryId const & QueuedCountry::GetCountryId() const
{
return m_countryId;
}
std::string QueuedCountry::GetRelativeUrl() const
{
auto const fileName = m_countryFile.GetFileName(m_fileType);
uint64_t diffVersion = 0;
if (m_fileType == MapFileType::Diff)
CHECK(m_diffsDataSource->VersionFor(m_countryId, diffVersion), ());
return downloader::GetFileDownloadUrl(fileName, m_currentDataVersion, diffVersion);
}
std::string QueuedCountry::GetFileDownloadPath() const
{
return platform::GetFileDownloadPath(m_currentDataVersion, m_dataDir, m_countryFile, m_fileType);
}
uint64_t QueuedCountry::GetDownloadSize() const
{
uint64_t size;
if (m_fileType == MapFileType::Diff)
{
CHECK(m_diffsDataSource->SizeToDownloadFor(m_countryId, size), ());
return size;
}
return GetRemoteSize(*m_diffsDataSource, m_countryFile);
}
void QueuedCountry::OnCountryInQueue() const
{
if (m_subscriber != nullptr)
m_subscriber->OnCountryInQueue(*this);
}
void QueuedCountry::OnStartDownloading() const
{
if (m_subscriber != nullptr)
m_subscriber->OnStartDownloading(*this);
}
void QueuedCountry::OnDownloadProgress(downloader::Progress const & progress) const
{
if (m_subscriber != nullptr)
m_subscriber->OnDownloadProgress(*this, progress);
}
void QueuedCountry::OnDownloadFinished(downloader::DownloadStatus status) const
{
if (m_subscriber != nullptr)
m_subscriber->OnDownloadFinished(*this, status);
}
bool QueuedCountry::operator==(CountryId const & countryId) const
{
return m_countryId == countryId;
}
} // namespace storage

View file

@ -0,0 +1,62 @@
#pragma once
#include "storage/diff_scheme/diffs_data_source.hpp"
#include "storage/storage_defines.hpp"
#include "platform/country_defines.hpp"
#include "platform/country_file.hpp"
#include "platform/downloader_defines.hpp"
#include <string>
namespace storage
{
class QueuedCountry
{
public:
class Subscriber
{
public:
virtual void OnCountryInQueue(QueuedCountry const & queuedCountry) = 0;
virtual void OnStartDownloading(QueuedCountry const & queuedCountry) = 0;
virtual void OnDownloadProgress(QueuedCountry const & queuedCountry, downloader::Progress const & progress) = 0;
virtual void OnDownloadFinished(QueuedCountry const & queuedCountry, downloader::DownloadStatus status) = 0;
protected:
virtual ~Subscriber() = default;
};
QueuedCountry(platform::CountryFile const & countryFile, CountryId const & m_countryId, MapFileType type,
int64_t currentDataVersion, std::string const & dataDir, diffs::DiffsSourcePtr const & diffs);
void Subscribe(Subscriber & subscriber);
void Unsubscribe();
void SetFileType(MapFileType type);
MapFileType GetFileType() const;
CountryId const & GetCountryId() const;
std::string GetRelativeUrl() const;
std::string GetFileDownloadPath() const;
uint64_t GetDownloadSize() const;
void OnCountryInQueue() const;
void OnStartDownloading() const;
void OnDownloadProgress(downloader::Progress const & progress) const;
void OnDownloadFinished(downloader::DownloadStatus status) const;
bool operator==(CountryId const & countryId) const;
private:
platform::CountryFile const m_countryFile;
CountryId const m_countryId;
MapFileType m_fileType;
int64_t m_currentDataVersion;
std::string m_dataDir;
diffs::DiffsSourcePtr m_diffsDataSource;
Subscriber * m_subscriber = nullptr;
};
} // namespace storage

View file

@ -0,0 +1,27 @@
#include "storage/routing_helpers.hpp"
namespace routing
{
std::unique_ptr<m4::Tree<routing::NumMwmId>> MakeNumMwmTree(NumMwmIds const & numMwmIds,
storage::CountryInfoGetter const & countryInfoGetter)
{
auto tree = std::make_unique<m4::Tree<NumMwmId>>();
numMwmIds.ForEachId([&](NumMwmId numMwmId)
{
auto const & countryName = numMwmIds.GetFile(numMwmId).GetName();
tree->Add(numMwmId, countryInfoGetter.GetLimitRectForLeaf(countryName));
});
return tree;
}
std::shared_ptr<routing::NumMwmIds> CreateNumMwmIds(storage::Storage const & storage)
{
auto numMwmIds = std::make_shared<routing::NumMwmIds>();
storage.ForEachCountry([&](storage::Country const & country) { numMwmIds->RegisterFile(country.GetFile()); });
return numMwmIds;
}
} // namespace routing

View file

@ -0,0 +1,17 @@
#pragma once
#include "routing_common/num_mwm_id.hpp"
#include "storage/country_info_getter.hpp"
#include "storage/storage.hpp"
#include "geometry/tree4d.hpp"
#include <memory>
namespace routing
{
std::unique_ptr<m4::Tree<routing::NumMwmId>> MakeNumMwmTree(NumMwmIds const & numMwmIds,
storage::CountryInfoGetter const & countryInfoGetter);
std::shared_ptr<NumMwmIds> CreateNumMwmIds(storage::Storage const & storage);
} // namespace routing

1963
libs/storage/storage.cpp Normal file

File diff suppressed because it is too large Load diff

733
libs/storage/storage.hpp Normal file
View file

@ -0,0 +1,733 @@
#pragma once
#include "storage/country.hpp"
#include "storage/country_name_getter.hpp"
#include "storage/country_tree.hpp"
#include "storage/diff_scheme/diffs_data_source.hpp"
#include "storage/downloading_policy.hpp"
#include "storage/map_files_downloader.hpp"
#include "storage/queued_country.hpp"
#include "storage/storage_defines.hpp"
#include "platform/downloader_defines.hpp"
#include "platform/local_country_file.hpp"
#include "base/cancellable.hpp"
#include "base/thread_checker.hpp"
#include "base/thread_pool_delayed.hpp"
#include <functional>
#include <list>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace storage_tests
{
struct UnitClass_StorageTest_DeleteCountry;
} // namespace storage_tests
namespace storage
{
struct CountryIdAndName
{
CountryId m_id;
std::string m_localName;
bool operator==(CountryIdAndName const & other) const
{
return m_id == other.m_id && m_localName == other.m_localName;
}
};
/// \brief Contains all properties for a node in the country tree.
/// It's applicable for expandable and not expandable node id.
struct NodeAttrs
{
NodeAttrs()
: m_mwmCounter(0)
, m_localMwmCounter(0)
, m_downloadingMwmCounter(0)
, m_mwmSize(0)
, m_localMwmSize(0)
, m_downloadingMwmSize(0)
, m_status(NodeStatus::Undefined)
, m_error(NodeErrorCode::NoError)
, m_present(false)
{}
/// If the node is expandable (a big country) |m_mwmCounter| is number of mwm files (leaves)
/// belonging to the node. If the node isn't expandable |m_mwmCounter| == 1.
/// Note. For every expandable node |m_mwmCounter| >= 2.
MwmCounter m_mwmCounter;
/// Number of mwms belonging to the node which have been downloaded.
MwmCounter m_localMwmCounter;
/// Number of leaves of the node which have been downloaded
/// plus which is in progress of downloading (zero or one)
/// plus which are staying in queue.
MwmCounter m_downloadingMwmCounter;
/// If it's not an expandable node, |m_mwmSize| is size of one mwm according to countries.txt.
/// Otherwise |m_mwmSize| is the sum of all mwm file sizes which belong to the group
/// according to countries.txt.
MwmSize m_mwmSize;
/// If it's not an expandable node, |m_localMwmSize| is size of one downloaded mwm.
/// Otherwise |m_localNodeSize| is the sum of all mwm file sizes which belong to the group and
/// have been downloaded.
MwmSize m_localMwmSize;
/// Size of leaves of the node which have been downloaded
/// plus which is in progress of downloading (zero or one)
/// plus which are staying in queue.
/// \note The size of leaves is the size is written in countries.txt.
MwmSize m_downloadingMwmSize;
/// The name of the node in a local language. That means the language dependent on
/// a device locale.
std::string m_nodeLocalName;
/// The description of the node in a local language. That means the language dependent on
/// a device locale.
std::string m_nodeLocalDescription;
/// Node id and local name of the parents of the node.
/// For the root |m_parentInfo| is empty.
/// Locale language is a language set by Storage::SetLocale().
/// \note Most number of nodes have only one parent. But in case of a disputed territories
/// an mwm could have two or even more parents. See Country description for details.
std::vector<CountryIdAndName> m_parentInfo;
/// Node id and local name of the first level parents (root children nodes)
/// if the node has first level parent(s). Otherwise |m_topmostParentInfo| is empty.
/// That means for the root and for the root children |m_topmostParentInfo| is empty.
/// Locale language is a language set by Storage::SetLocale().
/// \note Most number of nodes have only first level parent. But in case of a disputed territories
/// an mwm could have two or even more parents. See Country description for details.
std::vector<CountryIdAndName> m_topmostParentInfo;
/// Progress of downloading for the node expandable or not. It reflects downloading progress in case of
/// downloading and updating mwm.
/// m_downloadingProgress.m_bytesDownloaded is number of downloaded bytes.
/// m_downloadingProgress.m_bytesTotal is size of file(s) in bytes to download.
/// So m_downloadingProgress.m_bytesDownloaded <= m_downloadingProgress.m_bytesTotal.
downloader::Progress m_downloadingProgress;
/// Status of group and leaf node.
/// For group nodes it's defined in the following way:
/// If an mwm in a group has Downloading status the group has Downloading status
/// Otherwise if an mwm in the group has InQueue status the group has InQueue status
/// Otherwise if an mwm in the group has Error status the group has Error status
/// Otherwise if an mwm in the group has OnDiskOutOfDate the group has OnDiskOutOfDate status
/// Otherwise if all the mwms in the group have OnDisk status the group has OnDisk status
/// Otherwise if all the mwms in the group have NotDownloaded status the group has NotDownloaded status
/// Otherwise (that means a part of mwms in the group has OnDisk and the other part has NotDownloaded status)
/// the group has Mixed status
NodeStatus m_status;
/// Error code of leaf node. In case of group node |m_error| == NodeErrorCode::NoError.
NodeErrorCode m_error;
/// Indicates if leaf mwm is currently downloaded and connected to storage.
/// Can be used to distinguish downloadable and updatable maps.
/// m_present == false for group mwms.
bool m_present;
};
/// \brief Statuses for a node in the country tree.
/// It's applicable for expandable and not expandable node id.
struct NodeStatuses
{
NodeStatus m_status;
NodeErrorCode m_error;
bool m_groupNode;
};
// This class is used for downloading, updating and deleting maps.
// Storage manages a queue of mwms to be downloaded.
// Every operation with this queue must be executed
// on the storage thread. In the current implementation, the storage
// thread coincides with the main (UI) thread.
// Downloading of only one mwm at a time is supported, so while the
// mwm at the top of the queue is being downloaded (or updated by
// applying a diff file) all other mwms have to wait.
class Storage final : public QueuedCountry::Subscriber
{
public:
using StartDownloadingCallback = std::function<void()>;
using UpdateCallback = std::function<void(storage::CountryId const &, LocalFilePtr const)>;
using DeleteCallback = std::function<bool(storage::CountryId const &, LocalFilePtr const)>;
using ChangeCountryFunction = std::function<void(CountryId const &)>;
using ProgressFunction = std::function<void(CountryId const &, downloader::Progress const &)>;
using DownloadingCountries = std::unordered_map<CountryId, downloader::Progress>;
private:
/// We support only one simultaneous request at the moment
std::unique_ptr<MapFilesDownloader> m_downloader;
/// Stores timestamp for update checks
int64_t m_currentVersion = 0;
CountryTree m_countries;
/// Set of mwm files which have been downloaded recently.
/// When a mwm file is downloaded it's added to |m_justDownloaded|.
/// Note. This set is necessary for implementation of downloading progress of
/// mwm group.
CountriesSet m_justDownloaded;
/// stores countries whose download has failed recently
CountriesSet m_failedCountries;
/// @todo Do we really store a list of local files here (of different versions)?
/// I suspect that only one at a time, old versions are deleted automatically.
std::map<CountryId, std::list<LocalFilePtr>> m_localFiles;
// World and WorldCoasts are fake countries, together with any custom mwm in data folder.
std::map<platform::CountryFile, LocalFilePtr> m_localFilesForFakeCountries;
// Since the diffs applying runs on a different thread, the result
// of diff application may return "Ok" when in fact the diff was
// cancelled. However, the storage thread knows for sure whether
// request was to apply or to cancel the diff, and this knowledge
// is represented by |m_diffsBeingApplied|.
std::unordered_map<CountryId, std::unique_ptr<base::Cancellable>> m_diffsBeingApplied;
std::vector<platform::LocalCountryFile> m_notAppliedDiffs;
diffs::DiffsSourcePtr m_diffsDataSource = std::make_shared<diffs::DiffsDataSource>();
DownloadingPolicy m_defaultDownloadingPolicy;
DownloadingPolicy * m_downloadingPolicy = &m_defaultDownloadingPolicy;
/// @name Communicate with GUI
//@{
int m_currentSlotId = 0;
struct CountryObservers
{
ChangeCountryFunction m_changeCountryFn;
ProgressFunction m_progressFn;
int m_slotId;
};
std::list<CountryObservers> m_observers;
//@}
// This function is called each time all files requested for a
// country are successfully downloaded.
UpdateCallback m_didDownload;
// This function is called each time all files for a
// country are deleted.
DeleteCallback m_willDelete;
// If |m_dataDir| is not empty Storage will create version directories and download maps in
// platform::WritableDir/|m_dataDir|/. Not empty |m_dataDir| can be used only for
// downloading maps to a special place but not for continue working with them from this place.
std::string m_dataDir;
bool m_integrityValidationEnabled = true;
// |m_downloadMapOnTheMap| is called when an end user clicks on download map or retry button
// on the map.
DownloadFn m_downloadMapOnTheMap;
CountryNameGetter m_countryNameGetter;
/**
* @brief Mapping from countryId to the list of names of
* geographical objects (such as countries) that encompass this countryId.
* @note Affiliations are inherited from ancestors of the countryId in country tree.
* Initialized with data of countries.txt (field "affiliations").
* Once filled, they are not changed.
*/
Affiliations m_affiliations;
CountryNameSynonyms m_countryNameSynonyms;
/// @todo This containers are empty for now, but probably will be used in future.
/// @{
MwmTopCityGeoIds m_mwmTopCityGeoIds;
MwmTopCountryGeoIds m_mwmTopCountryGeoIds;
/// @}
ThreadChecker m_threadChecker;
bool m_needToStartDeferredDownloading = false;
StartDownloadingCallback m_startDownloadingCallback;
DownloadingCountries m_downloadingCountries;
void LoadCountriesFile(std::string const & pathToCountriesFile);
void ReportProgress(CountryId const & countryId, downloader::Progress const & p);
void ReportProgressForHierarchy(CountryId const & countryId, downloader::Progress const & leafProgress);
// QueuedCountry::Subscriber overrides:
void OnCountryInQueue(QueuedCountry const & queuedCountry) override;
void OnStartDownloading(QueuedCountry const & queuedCountry) override;
/// Called on the main thread by MapFilesDownloader when
/// downloading of a map file succeeds/fails.
void OnDownloadFinished(QueuedCountry const & queuedCountry, downloader::DownloadStatus status) override;
/// Periodically called on the main thread by MapFilesDownloader
/// during the downloading process.
void OnDownloadProgress(QueuedCountry const & queuedCountry, downloader::Progress const & progress) override;
void RegisterDownloadedFiles(CountryId const & countryId, MapFileType type);
void OnMapDownloadFinished(CountryId const & countryId, downloader::DownloadStatus status, MapFileType type);
/// Dummy ctor for private use only.
explicit Storage(int);
public:
ThreadChecker const & GetThreadChecker() const { return m_threadChecker; }
/// \brief Storage will create its directories in Writable Directory
/// (gotten with platform::WritableDir) by default.
/// \param pathToCountriesFile is a name of countries.txt file.
/// \param dataDir If |dataDir| is not empty Storage will create its directory in WritableDir/|dataDir|.
/// \note if |dataDir| is not empty the instance of Storage can be used only for downloading map files
/// but not for continue working with them.
/// If |dataDir| is not empty the work flow is
/// * create a instance of Storage with a special countries.txt and |dataDir|
/// * download some maps to WritableDir/|dataDir|
/// * destroy the instance of Storage and move the downloaded maps to proper place
Storage(std::string const & pathToCountriesFile = COUNTRIES_FILE, std::string const & dataDir = std::string());
/// \brief This constructor should be used for testing only.
Storage(std::string const & referenceCountriesTxtJsonForTesting,
std::unique_ptr<MapFilesDownloader> mapDownloaderForTesting);
void Init(UpdateCallback didDownload, DeleteCallback willDelete);
void SetDownloadingPolicy(DownloadingPolicy * policy);
bool CheckFailedCountries(CountriesVec const & countries) const;
/// @name Countries update functions. Public for unit tests.
/// @{
void RunCountriesCheckAsync();
/// @return 0 If error.
int64_t ParseIndexAndGetDataVersion(std::string const & index) const;
void ApplyCountries(std::string const & countriesBuffer, Storage & storage);
/// @}
/// \brief Returns root country id of the country tree.
CountryId const GetRootId() const;
/// \param childIds is filled with children node ids by a parent. For example GetChildren(GetRootId())
/// returns in param all countries ids. It's content of map downloader list by default.
void GetChildren(CountryId const & parent, CountriesVec & childIds) const;
/// \brief Fills |downloadedChildren| and |availChildren| with children of parent.
/// If a direct child of |parent| contains at least one downloaded mwm
/// the mwm id of the child will be added to |downloadedChildren|.
/// If not, the mwm id the child will not be added to |availChildren|.
/// \param parent is a parent acoording to countries.txt or cournties_migrate.txt.
/// \param downloadedChildren children partly or fully downloaded.
/// \param availChildren fully available children. None of its files have been downloaded.
/// \param keepAvailableChildren keeps all children in |availChildren| otherwise downloaded
/// children will be removed from |availChildren|.
/// \note. This method puts to |downloadedChildren| and |availChildren| only real maps (and its ancestors)
/// which have been written in coutries.txt or cournties_migrate.txt.
/// It means the method does not put to its params neither custom maps generated by user
/// nor World.mwm and WorldCoasts.mwm.
void GetChildrenInGroups(CountryId const & parent, CountriesVec & downloadedChildren, CountriesVec & availChildren,
bool keepAvailableChildren = false) const;
/// \brief Fills |queuedChildren| with children of |parent| if they (or thier childen) are in |m_queue|.
/// \note For group node children if one of child's ancestor has status
/// NodeStatus::Downloading or NodeStatus::InQueue the child is considered as a queued child
/// and will be added to |queuedChildren|.
void GetQueuedChildren(CountryId const & parent, CountriesVec & queuedChildren) const;
/// \brief Fills |path| with list of CountryId corresponding with path to the root of hierachy.
/// \param groupNode is start of path, can't be a leaf node.
/// \param path is resulting array of CountryId.
void GetGroupNodePathToRoot(CountryId const & groupNode, CountriesVec & path) const;
/// \brief Fills |nodes| with CountryIds of topmost nodes for this |countryId|.
/// \param level is distance from top level except root.
/// For disputed territories all possible owners will be added.
/// Puts |countryId| to |nodes| when |level| is greater than the level of |countryId|.
void GetTopmostNodesFor(CountryId const & countryId, CountriesVec & nodes, size_t level = 0) const;
/// \brief Returns topmost country id prior root id or |countryId| itself, if it's already
/// a topmost node or disputed territory id if |countryId| is a disputed territory or belongs to
/// disputed territory.
CountryId const GetTopmostParentFor(CountryId const & countryId) const;
/// \brief Returns parent id for node if node has single parent. Otherwise (if node is disputed
/// territory and has multiple parents or does not exist) returns empty CountryId
CountryId const GetParentIdFor(CountryId const & countryId) const;
/// \brief Returns current version for mwms which are used by storage.
inline int64_t GetCurrentDataVersion() const { return m_currentVersion; }
/// \brief Returns true if the node with countryId has been downloaded and false othewise.
/// If countryId is expandable returns true if all mwms which belongs to it have downloaded.
/// Returns false if countryId is an unknown string.
/// \note The method return false for custom maps generated by user
/// and World.mwm and WorldCoasts.mwm.
bool IsNodeDownloaded(CountryId const & countryId) const;
/// \brief Returns true if the last version of countryId has been downloaded.
bool HasLatestVersion(CountryId const & countryId) const;
/// \brief Returns true if the version of countryId can be used to update maps.
bool IsAllowedToEditVersion(CountryId const & countryId) const;
/// Returns version of downloaded mwm or zero.
int64_t GetVersion(CountryId const & countryId) const;
/// \brief Gets all the attributes for a node by its |countryId|.
/// \param |nodeAttrs| is filled with attributes in this method.
void GetNodeAttrs(CountryId const & countryId, NodeAttrs & nodeAttrs) const;
/// \brief Gets a short list of node attributes by its |countriId|.
/// \note This method works quicklier than GetNodeAttrs().
void GetNodeStatuses(CountryId const & countryId, NodeStatuses & nodeStatuses) const;
std::string GetNodeLocalName(CountryId const & countryId) const { return m_countryNameGetter(countryId); }
/// \brief Downloads/update one node (expandable or not) by countryId.
/// If node is expandable downloads/update all children (grandchildren) by the node
/// until they haven't been downloaded before.
void DownloadNode(CountryId const & countryId, bool isUpdate = false);
/// \brief Delete node with all children (expandable or not).
void DeleteNode(CountryId const & countryId);
/// \brief Updates one node. It works for leaf and group mwms.
/// \note If you want to update all the maps and this update is without changing
/// borders or hierarchy just call UpdateNode(GetRootId()).
void UpdateNode(CountryId const & countryId);
/// \brief If the downloading a new node is in process cancels downloading the node and deletes
/// the downloaded part of the map. If the map is in queue, remove the map from the queue.
/// If the downloading a updating map is in process cancels the downloading,
/// deletes the downloaded part of the map and leaves as is the old map (before the update)
/// had been downloaded. It works for leaf and for group mwms.
void CancelDownloadNode(CountryId const & countryId);
/// \brief Downloading process could be interupted because of bad internet connection
/// and some other reason.
/// In that case user could want to recover it. This method is done for it.
/// This method works with leaf and group mwm.
/// In case of a group mwm this method retries downloading all mwm in m_failedCountries list
/// which in the subtree with root |countryId|.
/// It means the call RetryDownloadNode(GetRootId()) retries all the failed mwms.
void RetryDownloadNode(CountryId const & countryId);
struct UpdateInfo
{
MwmCounter m_numberOfMwmFilesToUpdate = 0;
MwmSize m_maxFileSizeInBytes = 0;
MwmSize m_totalDownloadSizeInBytes = 0;
// Difference size in bytes between before update and after update.
int64_t m_sizeDifference = 0;
};
/// \brief Get information for mwm update button.
/// \return true if updateInfo is filled correctly and false otherwise.
bool GetUpdateInfo(CountryId const & countryId, UpdateInfo & updateInfo) const;
/// @name This functions should be called from 'main' thread only to avoid races.
/// @{
/// @return Pointer that will be stored for later use.
Affiliations const * GetAffiliations() const;
CountryNameSynonyms const & GetCountryNameSynonyms() const;
MwmTopCityGeoIds const & GetMwmTopCityGeoIds() const;
std::vector<base::GeoObjectId> GetTopCountryGeoIds(CountryId const & countryId) const;
/// @}
/// For each node with \a root subtree (including).
template <class ToDo>
void ForEachInSubtree(CountryId const & root, ToDo && toDo) const;
template <class ToDo>
void ForEachAncestorExceptForTheRoot(CountryId const & childId, ToDo && toDo) const;
template <class ToDo>
/// For each leaf country excluding Worlds.
void ForEachCountry(ToDo && toDo) const;
/// \brief Sets callback which will be called in case of a click on download map button on the map.
void SetCallbackForClickOnDownloadMap(DownloadFn & downloadFn);
/// \brief Calls |m_downloadMapOnTheMap| if one has been set.
/// \param |countryId| is country id of a leaf. That means it's a file name.
/// \note This method should be called for a click of download map button
/// and for a click for retry downloading map button on the map.
void DoClickOnDownloadMap(CountryId const & countryId);
//@}
/// \returns real (not fake) local maps contained in countries.txt.
/// So this method does not return custom user local maps and World and WorldCoasts country id.
// void GetLocalRealMaps(CountriesVec & localMaps) const;
/// Do we have downloaded countries
bool HaveDownloadedCountries() const;
/// Delete local maps and aggregate their Id if needed
void DeleteAllLocalMaps(CountriesVec * existedCountries = nullptr);
// Clears local files registry and downloader's queue.
void Clear();
/// Used in Android to get absent Worlds files to download.
/// @param[out] res Out vector, empty if all files are present some or error occured.
/// @return WorldStatus:
enum class WorldStatus
{
READY = 0, ///< Ready to download or all files are present if \a res is empty
WAS_MOVED, ///< All World files are present and one or more files was moved, \a res is empty.
ERROR_CREATE_FOLDER, ///< Error when creating folder
ERROR_MOVE_FILE ///< Error when trying to move World file
};
WorldStatus GetForceDownloadWorlds(std::vector<platform::CountryFile> & res) const;
// 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(bool enableDiffs = false);
// Returns list of all local maps, including fake countries (World*.mwm).
void GetLocalMaps(std::vector<LocalFilePtr> & maps) const;
// Returns number of downloaded maps (files), excluding fake countries (World*.mwm).
size_t GetDownloadedFilesCount() const;
/// Guarantees that change and progress are called in the main thread context.
/// @return unique identifier that should be used with Unsubscribe function
int Subscribe(ChangeCountryFunction change, ProgressFunction progress);
void Unsubscribe(int slotId);
/// Returns information about selected counties downloading progress.
/// |countries| - watched CountryId, ONLY leaf expected.
downloader::Progress GetOverallProgress(CountriesVec const & countries) const;
Country const & CountryLeafByCountryId(CountryId const & countryId) const;
Country const & CountryByCountryId(CountryId const & countryId) const;
/// @todo Proxy functions for future, to distinguish CountryId from regular file name.
/// @{
CountryId const & FindCountryId(platform::LocalCountryFile const & localFile) const
{
return localFile.GetCountryName();
}
CountryId const & FindCountryIdByFile(std::string const & name) const { return name; }
/// @}
// Returns true iff |countryId| exists as a node in the tree.
bool IsNode(CountryId const & countryId) const;
/// @return true iff \a countryId is a leaf of the tree.
bool IsLeaf(CountryId const & countryId) const;
// Returns true iff |countryId| is an inner node of the tree.
bool IsInnerNode(CountryId const & countryId) const;
LocalAndRemoteSize CountrySizeInBytes(CountryId const & countryId) const;
MwmSize GetRemoteSize(platform::CountryFile const & file) const;
platform::CountryFile const & GetCountryFile(CountryId const & countryId) const;
LocalFilePtr GetLatestLocalFile(platform::CountryFile const & countryFile) const;
LocalFilePtr GetLatestLocalFile(CountryId const & countryId) const;
/// Slow version, but checks if country is out of date
Status CountryStatusEx(CountryId const & countryId) const;
/// Puts country denoted by countryId into the downloader's queue.
/// During downloading process notifies observers about downloading
/// progress and status changes.
void DownloadCountry(CountryId const & countryId, MapFileType type);
/// Removes country files (for all versions) from the device.
/// Notifies observers about country status change.
void DeleteCountry(CountryId const & countryId, MapFileType type);
/// Removes country files of a particular version from the device.
/// Notifies observers about country status change.
void DeleteCustomCountryVersion(platform::LocalCountryFile const & localFile);
bool IsDownloadInProgress() const;
/// @param[out] res Populated with oudated countries.
// void GetOutdatedCountries(std::vector<Country const *> & countries) const;
/// Sets and gets locale, which is used to get localized counries names
void SetLocale(std::string const & locale) { m_countryNameGetter.SetLocale(locale); }
std::string GetLocale() const { return m_countryNameGetter.GetLocale(); }
// for testing:
void SetEnabledIntegrityValidationForTesting(bool enabled);
void SetDownloaderForTesting(std::unique_ptr<MapFilesDownloader> downloader);
void SetCurrentDataVersionForTesting(int64_t currentVersion);
void SetDownloadingServersForTesting(std::vector<std::string> const & downloadingUrls);
void SetLocaleForTesting(std::string const & jsonBuffer, std::string const & locale);
/// Returns true if the diff scheme is available and all local outdated maps can be updated via diffs.
// bool IsPossibleToAutoupdate() const;
void SetStartDownloadingCallback(StartDownloadingCallback const & cb);
std::string GetFilePath(CountryId const & countryId, MapFileType file) const;
void RestoreDownloadQueue();
protected:
void OnFinishDownloading();
private:
friend struct storage_tests::UnitClass_StorageTest_DeleteCountry;
void SaveDownloadQueue();
// Returns true when country is in the downloader's queue.
bool IsCountryInQueue(CountryId const & countryId) const;
// Returns true if we started the diff applying procedure for an mwm with countryId.
bool IsDiffApplyingInProgressToCountry(CountryId const & countryId) const;
// Returns local country files of a particular version, or wrapped
// nullptr if there're no country files corresponding to the
// version.
LocalFilePtr GetLocalFile(CountryId const & countryId, 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(LocalFilePtr localFile);
// Registers disk files for a country. This method must be used only
// for real (listed in countries.txt) countries.
void RegisterLocalFile(platform::LocalCountryFile const & localFile);
// Removes disk files for all versions of a country.
void DeleteCountryFiles(CountryId const & countryId, MapFileType type, bool deferredDelete);
// Removes country files from downloader.
bool DeleteCountryFilesFromDownloader(CountryId const & countryId);
// Returns a path to a place on disk downloader can use for downloaded files.
std::string GetFileDownloadPath(CountryId const & countryId, MapFileType file) const;
/// Fast version, doesn't check if country is out of date
Status CountryStatus(CountryId const & countryId) const;
/// Returns status for a node (group node or not).
StatusAndError GetNodeStatus(CountryTree::Node const & node) const;
/// Returns status for a node (group node or not).
/// Fills |disputedTeritories| with all disputed teritories in subtree with the root == |node|.
StatusAndError GetNodeStatusInfo(CountryTree::Node const & node,
std::vector<std::pair<CountryId, NodeStatus>> & disputedTeritories,
bool isDisputedTerritoriesCounted) const;
void NotifyStatusChanged(CountryId const & countryId);
void NotifyStatusChangedForHierarchy(CountryId const & countryId);
/// Calculates progress of downloading for expandable nodes in country tree.
/// |descendants| All descendants of the parent node.
downloader::Progress CalculateProgress(CountriesVec const & descendants) const;
template <class ToDo>
void ForEachAncestorExceptForTheRoot(CountryTree::NodesBufferT const & nodes, ToDo && toDo) const;
/// @return true if |node.Value().Name()| is a disputed territory and false otherwise.
bool IsDisputed(CountryTree::Node const & node) const;
/// @return true iff \a node is a country MWM leaf of the tree.
static bool IsCountryLeaf(CountryTree::Node const & node);
static bool IsWorldCountryID(CountryId const & country);
void OnMapDownloadFailed(CountryId const & countryId);
// void LoadDiffScheme();
void ApplyDiff(CountryId const & countryId, std::function<void(bool isSuccess)> const & fn);
using IsDiffAbsentForCountry = std::function<bool(CountryId const & id)>;
void SetMapSchemeForCountriesWithAbsentDiffs(IsDiffAbsentForCountry const & isAbsent);
void AbortDiffScheme();
// Should be called once on startup, downloading process should be suspended until this method
// was not called. Do not call this method manually.
void OnDiffStatusReceived(diffs::NameDiffInfoMap && diffs);
};
CountriesSet GetQueuedCountries(QueueInterface const & queue);
template <class ToDo>
void Storage::ForEachInSubtree(CountryId const & root, ToDo && toDo) const
{
CountryTree::Node const * const rootNode = m_countries.FindFirst(root);
if (rootNode == nullptr)
{
ASSERT(false, ("CountryId =", root, "not found in m_countries."));
return;
}
rootNode->ForEachInSubtree([&toDo](CountryTree::Node const & node)
{
Country const & value = node.Value();
toDo(value.Name(), value.GetSubtreeMwmCounter() != 1 /* groupNode. */);
});
}
/// Calls functor |toDo| with signature
/// void(const CountryId const & parentId, CountriesVec const & descendantCountryId)
/// for each ancestor except for the main root of the tree in order from the leaf to the root.
/// Note. In case of disputable territories several nodes with the same name may be
/// present in the country tree. In that case ForEachAncestorExceptForTheRoot calls
/// |toDo| for parents of each way to the root in the country tree. In case of diamond
/// trees toDo is called for common part of ways to the root only once.
template <class ToDo>
void Storage::ForEachAncestorExceptForTheRoot(CountryId const & countryId, ToDo && toDo) const
{
CountryTree::NodesBufferT nodes;
m_countries.Find(countryId, nodes);
if (nodes.empty())
{
ASSERT(false, ("CountryId =", countryId, "not found in m_countries."));
return;
}
ForEachAncestorExceptForTheRoot(nodes, std::forward<ToDo>(toDo));
}
template <class ToDo>
void Storage::ForEachAncestorExceptForTheRoot(CountryTree::NodesBufferT const & nodes, ToDo && toDo) const
{
std::set<CountryTree::Node const *> visitedAncestors;
// In most cases nodes.size() == 1. In case of disputable territories nodes.size()
// may be more than one. It means |childId| is present in the country tree more than once.
for (auto const & node : nodes)
{
node->ForEachAncestorExceptForTheRoot([&](CountryTree::Node const & node)
{
CountryId const ancestorId = node.Value().Name();
if (visitedAncestors.find(&node) != visitedAncestors.end())
return; // The node was visited before because countryId is present in the tree more
// than once.
visitedAncestors.insert(&node);
toDo(ancestorId, node);
});
}
}
template <class ToDo>
void Storage::ForEachCountry(ToDo && toDo) const
{
m_countries.GetRoot().ForEachInSubtree([&](CountryTree::Node const & node)
{
if (IsCountryLeaf(node))
toDo(node.Value());
});
}
} // namespace storage

View file

@ -0,0 +1,90 @@
#include "storage/storage_defines.hpp"
#include "base/assert.hpp"
#include <sstream>
using namespace std;
using namespace string_literals;
namespace storage
{
storage::CountryId const kInvalidCountryId;
bool IsCountryIdValid(CountryId const & countryId)
{
return countryId != kInvalidCountryId;
}
string DebugPrint(Status status)
{
switch (status)
{
case Status::Undefined: return "EUndefined"s;
case Status::OnDisk: return "OnDisk"s;
case Status::NotDownloaded: return "NotDownloaded"s;
case Status::DownloadFailed: return "DownloadFailed"s;
case Status::Downloading: return "Downloading"s;
case Status::Applying: return "Applying"s;
case Status::InQueue: return "InQueue"s;
case Status::UnknownError: return "Unknown"s;
case Status::OnDiskOutOfDate: return "OnDiskOutOfDate"s;
case Status::OutOfMemFailed: return "OutOfMemFailed"s;
}
UNREACHABLE();
}
string DebugPrint(NodeStatus status)
{
switch (status)
{
case NodeStatus::Undefined: return "Undefined"s;
case NodeStatus::Error: return "Error"s;
case NodeStatus::OnDisk: return "OnDisk"s;
case NodeStatus::NotDownloaded: return "NotDownloaded"s;
case NodeStatus::Downloading: return "Downloading"s;
case NodeStatus::Applying: return "Applying"s;
case NodeStatus::InQueue: return "InQueue"s;
case NodeStatus::OnDiskOutOfDate: return "OnDiskOutOfDate"s;
case NodeStatus::Partly: return "Partly"s;
}
UNREACHABLE();
}
string DebugPrint(NodeErrorCode status)
{
switch (status)
{
case NodeErrorCode::NoError: return "NoError"s;
case NodeErrorCode::UnknownError: return "UnknownError"s;
case NodeErrorCode::OutOfMemFailed: return "OutOfMemFailed"s;
case NodeErrorCode::NoInetConnection: return "NoInetConnection"s;
}
UNREACHABLE();
}
StatusAndError ParseStatus(Status innerStatus)
{
switch (innerStatus)
{
case Status::Undefined: return StatusAndError(NodeStatus::Undefined, NodeErrorCode::NoError);
case Status::OnDisk: return StatusAndError(NodeStatus::OnDisk, NodeErrorCode::NoError);
case Status::NotDownloaded: return StatusAndError(NodeStatus::NotDownloaded, NodeErrorCode::NoError);
case Status::DownloadFailed: return StatusAndError(NodeStatus::Error, NodeErrorCode::NoInetConnection);
case Status::Downloading: return StatusAndError(NodeStatus::Downloading, NodeErrorCode::NoError);
case Status::Applying: return StatusAndError(NodeStatus::Applying, NodeErrorCode::NoError);
case Status::InQueue: return StatusAndError(NodeStatus::InQueue, NodeErrorCode::NoError);
case Status::UnknownError: return StatusAndError(NodeStatus::Error, NodeErrorCode::UnknownError);
case Status::OnDiskOutOfDate: return StatusAndError(NodeStatus::OnDiskOutOfDate, NodeErrorCode::NoError);
case Status::OutOfMemFailed: return StatusAndError(NodeStatus::Error, NodeErrorCode::OutOfMemFailed);
}
UNREACHABLE();
}
string DebugPrint(StatusAndError statusAndError)
{
ostringstream out;
out << "StatusAndError[" << DebugPrint(statusAndError.status) << ", " << DebugPrint(statusAndError.error) << "]";
return out.str();
}
} // namespace storage

View file

@ -0,0 +1,92 @@
#pragma once
#include "platform/local_country_file.hpp"
#include "base/geo_object_id.hpp"
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace storage
{
using CountryId = std::string;
using CountriesSet = std::set<CountryId>;
using CountriesVec = std::vector<CountryId>;
using LocalFilePtr = std::shared_ptr<platform::LocalCountryFile>;
using OldMwmMapping = std::map<CountryId, CountriesSet>;
/// Map from key affiliation words into CountryIds.
using Affiliations = std::unordered_map<std::string, std::vector<CountryId>>;
/// Map from country name synonyms and old names into CountryId.
using CountryNameSynonyms = std::unordered_map<std::string, CountryId>;
/// Map from CountryId into city GeoObject id.
using MwmTopCityGeoIds = std::unordered_map<CountryId, base::GeoObjectId>;
using MwmTopCountryGeoIds = std::unordered_map<CountryId, std::vector<base::GeoObjectId>>;
extern storage::CountryId const kInvalidCountryId;
// @TODO(bykoianko) Check in country tree if the countryId is valid.
bool IsCountryIdValid(CountryId const & countryId);
/// Inner status which is used inside Storage class
enum class Status : uint8_t
{
Undefined = 0,
OnDisk, /**< Downloaded mwm(s) is up to date. No need to update it. */
NotDownloaded, /**< Mwm can be download but not downloaded yet. */
DownloadFailed, /**< Downloading failed because no internet connection. */
Downloading, /**< Downloading a new mwm or updating an old one. */
Applying, /**< Applying downloaded diff for an old mwm. */
InQueue, /**< A mwm is waiting for downloading in the queue. */
UnknownError, /**< Downloading failed because of unknown error. */
OnDiskOutOfDate, /**< An update for a downloaded mwm is ready according to counties.txt. */
OutOfMemFailed, /**< Downloading failed because it's not enough memory */
};
std::string DebugPrint(Status status);
/// \note The order of enum items is important. It is used in Storage::NodeStatus method.
/// If it's necessary to add more statuses it's better to add to the end.
enum class NodeStatus
{
Undefined,
Downloading, /**< Downloading a new mwm or updating an old one. */
Applying, /**< Applying downloaded diff for an old mwm. */
InQueue, /**< An mwm is waiting for downloading in the queue. */
Error, /**< An error happened while downloading */
OnDiskOutOfDate, /**< An update for a downloaded mwm is ready according to counties.txt. */
OnDisk, /**< Downloaded mwm(s) is up to date. No need to update it. */
NotDownloaded, /**< An mwm can be downloaded but not downloaded yet. */
Partly, /**< Leafs of group node has a mix of NotDownloaded and OnDisk status. */
};
std::string DebugPrint(NodeStatus status);
enum class NodeErrorCode
{
NoError,
UnknownError, /**< Downloading failed because of unknown error. */
OutOfMemFailed, /**< Downloading failed because it's not enough memory */
NoInetConnection, /**< Downloading failed because internet connection was interrupted */
};
std::string DebugPrint(NodeErrorCode status);
struct StatusAndError
{
StatusAndError(NodeStatus nodeStatus, NodeErrorCode nodeError) : status(nodeStatus), error(nodeError) {}
bool operator==(StatusAndError const & other) const { return other.status == status && other.error == error; }
NodeStatus status;
NodeErrorCode error;
};
std::string DebugPrint(StatusAndError statusAndError);
StatusAndError ParseStatus(Status innerStatus);
} // namespace storage
using DownloadFn = std::function<void(storage::CountryId const &)>;

View file

@ -0,0 +1,87 @@
#include "storage/storage_helpers.hpp"
#include "storage/country_info_getter.hpp"
#include "storage/storage.hpp"
#include "platform/platform.hpp"
#include "std/target_os.hpp"
namespace storage
{
bool IsPointCoveredByDownloadedMaps(m2::PointD const & position, Storage const & storage,
CountryInfoGetter const & countryInfoGetter)
{
return storage.IsNodeDownloaded(countryInfoGetter.GetRegionCountryId(position));
}
bool IsDownloadFailed(Status status)
{
return status == Status::DownloadFailed || status == Status::OutOfMemFailed || status == Status::UnknownError;
}
bool IsEnoughSpaceForDownload(MwmSize mwmSize)
{
// Additional size which is necessary to have on flash card to download file of mwmSize bytes.
MwmSize constexpr kExtraSizeBytes = 10 * 1024 * 1024;
return GetPlatform().GetWritableStorageStatus(mwmSize + kExtraSizeBytes) == Platform::TStorageStatus::STORAGE_OK;
}
bool IsEnoughSpaceForDownload(CountryId const & countryId, Storage const & storage)
{
NodeAttrs nodeAttrs;
storage.GetNodeAttrs(countryId, nodeAttrs);
return IsEnoughSpaceForDownload(nodeAttrs.m_mwmSize);
}
bool IsEnoughSpaceForUpdate(CountryId const & countryId, Storage const & storage)
{
Storage::UpdateInfo updateInfo;
storage.GetUpdateInfo(countryId, updateInfo);
/// @todo Review this logic when Storage::ApplyDiff will be restored.
// 1. For unlimited concurrent downloading process with "download and apply diff" strategy:
// - download and save all MWMs or Diffs = m_totalDownloadSizeInBytes
// - max MWM file size to apply diff patch (patches are applying one-by-one) = m_maxFileSizeInBytes
// - final size difference between old and new MWMs = m_sizeDifference
[[maybe_unused]] MwmSize const diff = updateInfo.m_sizeDifference > 0 ? updateInfo.m_sizeDifference : 0;
// return IsEnoughSpaceForDownload(std::max(diff, updateInfo.m_totalDownloadSizeInBytes) +
// updateInfo.m_maxFileSizeInBytes);
// 2. For the current "download and replace" strategy:
// - Android and Desktop has 1 simultaneous download
// - iOS has unlimited simultaneous downloads
#ifdef OMIM_OS_IPHONE
return IsEnoughSpaceForDownload(updateInfo.m_totalDownloadSizeInBytes);
#else
return IsEnoughSpaceForDownload(diff + updateInfo.m_maxFileSizeInBytes);
#endif // OMIM_OS_IPHONE
}
m2::RectD CalcLimitRect(CountryId const & countryId, Storage const & storage,
CountryInfoGetter const & countryInfoGetter)
{
m2::RectD boundingBox;
auto const accumulator = [&countryInfoGetter, &boundingBox](CountryId const & descendantId, bool groupNode)
{
if (!groupNode)
boundingBox.Add(countryInfoGetter.GetLimitRectForLeaf(descendantId));
};
storage.ForEachInSubtree(countryId, accumulator);
ASSERT(boundingBox.IsValid(), ());
return boundingBox;
}
MwmSize GetRemoteSize(diffs::DiffsDataSource const & diffsDataSource, platform::CountryFile const & file)
{
uint64_t size;
if (diffsDataSource.SizeFor(file.GetName(), size))
return size;
return file.GetRemoteSize();
}
} // namespace storage

View file

@ -0,0 +1,35 @@
#pragma once
#include "storage/diff_scheme/diffs_data_source.hpp"
#include "storage/storage_defines.hpp"
#include "platform/country_defines.hpp"
#include "platform/country_file.hpp"
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
namespace storage
{
class CountryInfoGetter;
class Storage;
/// \returns true if |position| is covered by a downloaded mwms and false otherwise.
/// \note |position| has coordinates in mercator.
/// \note This method takes into acount only maps enumerated in countries.txt.
bool IsPointCoveredByDownloadedMaps(m2::PointD const & position, Storage const & storage,
CountryInfoGetter const & countryInfoGetter);
bool IsDownloadFailed(Status status);
bool IsEnoughSpaceForDownload(MwmSize mwmSize);
bool IsEnoughSpaceForDownload(CountryId const & countryId, Storage const & storage);
bool IsEnoughSpaceForUpdate(CountryId const & countryId, Storage const & storage);
/// \brief Calculates limit rect for |countryId| (expandable or not).
/// \returns bounding box in mercator coordinates.
m2::RectD CalcLimitRect(CountryId const & countryId, Storage const & storage,
CountryInfoGetter const & countryInfoGetter);
MwmSize GetRemoteSize(diffs::DiffsDataSource const & diffsDataSource, platform::CountryFile const & file);
} // namespace storage

View file

@ -0,0 +1,21 @@
project(storage_integration_tests)
set(SRC
download_calc_size_test.cpp
lightweight_matching_tests.cpp
storage_3levels_tests.cpp
storage_downloading_tests.cpp
storage_group_download_tests.cpp
storage_http_tests.cpp
storage_update_tests.cpp
test_defines.cpp
test_defines.hpp
)
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT REQUIRE_SERVER)
target_link_libraries(${PROJECT_NAME}
platform_tests_support
storage
map
)

View file

@ -0,0 +1,71 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "storage/storage.hpp"
#include "platform/downloader_defines.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
namespace download_calc_size_test
{
using namespace storage;
void InitStorage(Storage & storage, Storage::UpdateCallback const & didDownload,
Storage::ProgressFunction const & progress)
{
auto const changeCountryFunction = [&](CountryId const & /* countryId */)
{
if (!storage.IsDownloadInProgress())
{
// End wait for downloading complete.
testing::StopEventLoop();
}
};
storage.Init(didDownload, [](CountryId const &, LocalFilePtr const) { return false; });
storage.RegisterAllLocalMaps();
storage.Subscribe(changeCountryFunction, progress);
storage.SetDownloadingServersForTesting({kTestWebServer});
}
UNIT_TEST(DownloadingTests_CalcOverallProgress)
{
WritableDirChanger writableDirChanger(storage::kMapTestDir);
// A bunch of small islands.
CountriesVec const kTestCountries = {"Kiribati", "Tokelau", "Niue", "Palau", "Pitcairn Islands"};
Storage s;
s.SetDownloadingServersForTesting({storage::kTestWebServer});
auto baseProgress = s.GetOverallProgress(kTestCountries);
TEST_EQUAL(baseProgress.m_bytesDownloaded, 0, ());
TEST_EQUAL(baseProgress.m_bytesTotal, 0, ());
for (auto const & country : kTestCountries)
baseProgress.m_bytesTotal += s.CountrySizeInBytes(country).second;
auto progressChanged =
[&s, &kTestCountries, &baseProgress](CountryId const & id, downloader::Progress const & /* progress */)
{
auto const currentProgress = s.GetOverallProgress(kTestCountries);
LOG_SHORT(LINFO, (id, "downloading progress:", currentProgress));
TEST_GREATER_OR_EQUAL(currentProgress.m_bytesDownloaded, baseProgress.m_bytesDownloaded, ());
baseProgress.m_bytesDownloaded = currentProgress.m_bytesDownloaded;
TEST_LESS_OR_EQUAL(currentProgress.m_bytesDownloaded, baseProgress.m_bytesTotal, ());
TEST_EQUAL(currentProgress.m_bytesTotal, baseProgress.m_bytesTotal, ());
};
InitStorage(s, [](storage::CountryId const &, LocalFilePtr const) {}, progressChanged);
for (auto const & countryId : kTestCountries)
s.DownloadNode(countryId);
testing::RunEventLoop();
}
} // namespace download_calc_size_test

View file

@ -0,0 +1,47 @@
#include "testing/testing.hpp"
#include "storage/country_info_reader_light.hpp"
#include "storage/storage_defines.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include <utility>
#include <vector>
namespace lightweight_matching_tests
{
double constexpr kStepInMercator = 1;
struct PointAndCountry
{
PointAndCountry(m2::PointD && pt, storage::CountryId && country) : m_pt(std::move(pt)), m_country(std::move(country))
{}
m2::PointD m_pt;
storage::CountryId m_country;
};
using lightweight::CountryInfoReader;
UNIT_CLASS_TEST(CountryInfoReader, LightweightMatching)
{
auto const reader = storage::CountryInfoReader::CreateCountryInfoReader(GetPlatform());
LOG(LINFO, ("Generating dataset..."));
std::vector<PointAndCountry> dataset;
for (auto x = mercator::Bounds::kMinX; x <= mercator::Bounds::kMaxX; x += kStepInMercator)
{
for (auto y = mercator::Bounds::kMinY; y <= mercator::Bounds::kMaxY; y += kStepInMercator)
{
m2::PointD pt(x, y);
dataset.emplace_back(std::move(pt), reader->GetRegionCountryId(pt));
}
}
LOG(LINFO, ("The dataset is generated. Dataset size:", dataset.size()));
for (auto const & sample : dataset)
TEST_EQUAL(GetRegionCountryId(sample.m_pt), sample.m_country, (sample.m_pt));
}
} // namespace lightweight_matching_tests

View file

@ -0,0 +1,95 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "map/framework.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "base/file_name_utils.hpp"
#include "base/scope_guard.hpp"
#include <algorithm>
#include <string>
#include "defines.hpp"
using namespace platform;
using namespace storage;
namespace
{
int GetLevelCount(Storage & storage, CountryId const & countryId)
{
CountriesVec children;
storage.GetChildren(countryId, children);
int level = 0;
for (auto const & child : children)
level = std::max(level, GetLevelCount(storage, child));
return 1 + level;
}
} // namespace
UNIT_TEST(SmallMwms_3levels_Test)
{
WritableDirChanger writableDirChanger(kMapTestDir);
Platform & platform = GetPlatform();
/// @todo So sick, but Framework.RoutingManager has so complicated logic with a bunch of
/// RunOnGui callbacks, so delete Framework also in RunOnGui.
auto * frm = new Framework(FrameworkParams(false /* m_enableDiffs */));
SCOPE_GUARD(deleteFramework, [frm]() { GetPlatform().RunTask(Platform::Thread::Gui, [frm]() { delete frm; }); });
auto & storage = frm->GetStorage();
std::string const version = strings::to_string(storage.GetCurrentDataVersion());
CountryId country = "Germany";
TEST_EQUAL(3, GetLevelCount(storage, country), ());
std::string const mapDir = base::JoinPath(platform.WritableDir(), version);
auto onProgressFn = [&](CountryId const & countryId, downloader::Progress const & /* progress */) {};
auto onChangeCountryFn = [&](CountryId const & countryId)
{
if (!storage.IsDownloadInProgress())
testing::StopEventLoop();
};
storage.Subscribe(onChangeCountryFn, onProgressFn);
storage.SetDownloadingServersForTesting({kTestWebServer});
/// @todo Download all Germany > 2GB takes hours here ..
country = "Kiribati";
NodeAttrs attrs;
storage.GetNodeAttrs(country, attrs);
TEST_EQUAL(attrs.m_status, NodeStatus::NotDownloaded, ());
Platform::FilesList files;
platform.GetFilesByExt(mapDir, DATA_FILE_EXTENSION, files);
TEST_EQUAL(0, files.size(), ());
storage.DownloadNode(country);
testing::RunEventLoop();
storage.GetNodeAttrs(country, attrs);
TEST_EQUAL(attrs.m_status, NodeStatus::OnDisk, ());
files.clear();
platform.GetFilesByExt(mapDir, DATA_FILE_EXTENSION, files);
TEST_GREATER(files.size(), 0, ());
storage.DeleteNode(country);
storage.GetNodeAttrs(country, attrs);
TEST_EQUAL(attrs.m_status, NodeStatus::NotDownloaded, ());
files.clear();
platform.GetFilesByExt(mapDir, DATA_FILE_EXTENSION, files);
TEST_EQUAL(0, files.size(), ());
}

View file

@ -0,0 +1,230 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "storage/storage.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/scoped_dir.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "coding/file_writer.hpp"
#include "coding/sha1.hpp"
#include "base/file_name_utils.hpp"
#include "base/scope_guard.hpp"
#include "base/string_utils.hpp"
#include "base/thread.hpp"
#include <cstdlib>
#include <exception>
#include <functional>
#include <string>
using namespace platform;
using namespace storage;
using namespace std;
using namespace std::placeholders;
// Uncomment to enable the test that requires network and downloads an mwm several times.
// #define TEST_INTEGRITY
#ifndef TEST_INTEGRITY_ITERATIONS
#define TEST_INTEGRITY_ITERATIONS 5
#endif
namespace
{
using Runner = Platform::ThreadRunner;
string const kCountryId = "Trinidad and Tobago";
class InterruptException : public exception
{};
void Update(CountryId const &, storage::LocalFilePtr const localCountryFile)
{
TEST_EQUAL(localCountryFile->GetCountryName(), kCountryId, ());
}
void ChangeCountry(Storage & storage, CountryId const & countryId)
{
TEST_EQUAL(countryId, kCountryId, ());
if (!storage.IsDownloadInProgress())
testing::StopEventLoop();
}
void InitStorage(Storage & storage, Storage::ProgressFunction const & onProgressFn)
{
storage.Init(Update, [](CountryId const &, storage::LocalFilePtr const) { return false; });
storage.RegisterAllLocalMaps();
storage.Subscribe(bind(&ChangeCountry, ref(storage), _1), onProgressFn);
storage.SetDownloadingServersForTesting({kTestWebServer});
}
} // namespace
UNIT_TEST(SmallMwms_ReDownloadExistedMWMIgnored_Test)
{
WritableDirChanger writableDirChanger(kMapTestDir);
Storage storage;
InitStorage(storage, [](CountryId const &, downloader::Progress const &) {});
TEST(!storage.IsDownloadInProgress(), ());
storage.DownloadNode(kCountryId);
TEST(storage.IsDownloadInProgress(), ());
testing::RunEventLoop();
TEST(!storage.IsDownloadInProgress(), ());
storage.DownloadNode(kCountryId);
TEST(!storage.IsDownloadInProgress(), ());
}
UNIT_CLASS_TEST(Runner, SmallMwms_InterruptDownloadResumeDownload_Test)
{
WritableDirChanger writableDirChanger(kMapTestDir);
// Start download but interrupt it
{
Storage storage;
auto const onProgressFn = [](CountryId const & countryId, downloader::Progress const & /* progress */)
{
TEST_EQUAL(countryId, kCountryId, ());
// Interrupt download
testing::StopEventLoop();
};
InitStorage(storage, onProgressFn);
TEST(!storage.IsDownloadInProgress(), ());
storage.DownloadNode(kCountryId);
testing::RunEventLoop();
TEST(storage.IsDownloadInProgress(), ());
NodeAttrs attrs;
storage.GetNodeAttrs(kCountryId, attrs);
TEST_EQUAL(NodeStatus::Downloading, attrs.m_status, ());
}
// Continue download
{
Storage storage;
bool onProgressIsCalled = false;
NodeAttrs onProgressAttrs;
auto const onProgressFn = [&](CountryId const & countryId, downloader::Progress const & /* progress */)
{
TEST_EQUAL(countryId, kCountryId, ());
if (onProgressIsCalled)
return;
onProgressIsCalled = true;
storage.GetNodeAttrs(kCountryId, onProgressAttrs);
testing::StopEventLoop();
};
InitStorage(storage, onProgressFn);
storage.Init([](CountryId const &, storage::LocalFilePtr const localCountryFile)
{
TEST_EQUAL(localCountryFile->GetCountryName(), kCountryId, ());
testing::StopEventLoop();
}, [](CountryId const &, storage::LocalFilePtr const) { return false; });
testing::RunEventLoop();
TEST(storage.IsDownloadInProgress(), ());
testing::RunEventLoop();
TEST_EQUAL(NodeStatus::Downloading, onProgressAttrs.m_status, ());
NodeAttrs attrs;
storage.GetNodeAttrs(kCountryId, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
}
}
#ifdef TEST_INTEGRITY
UNIT_CLASS_TEST(Runner, DownloadIntegrity_Test)
{
WritableDirChanger writableDirChanger(kMapTestDir);
string mapPath;
coding::SHA1::Hash mapHash;
{
SCOPE_GUARD(deleteTestFileGuard, bind(&FileWriter::DeleteFileX, ref(mapPath)));
Storage storage(COUNTRIES_FILE);
InitStorage(storage, [](CountryId const & countryId, LocalAndRemoteSize const & mapSize) {});
TEST(!storage.IsDownloadInProgress(), ());
storage.DownloadNode(kCountryId);
TEST(storage.IsDownloadInProgress(), ());
testing::RunEventLoop();
auto localFile = storage.GetLatestLocalFile(kCountryId);
mapPath = localFile->GetPath(MapFileType::Map);
mapHash = coding::SHA1::Calculate(mapPath);
}
TEST_NOT_EQUAL(mapHash, coding::SHA1::Hash(), ());
uint32_t constexpr kIterationsCount = TEST_INTEGRITY_ITERATIONS;
for (uint32_t i = 0; i < kIterationsCount; ++i)
{
// Downloading with interruption.
uint32_t constexpr kInterruptionsCount = 10;
for (uint32_t j = 0; j < kInterruptionsCount; ++j)
{
SCOPE_GUARD(deleteTestFileGuard, bind(&FileWriter::DeleteFileX, ref(mapPath)));
Storage storage(COUNTRIES_FILE);
auto onProgressFn = [i, j](CountryId const & countryId, LocalAndRemoteSize const & mapSize)
{
TEST_EQUAL(countryId, kCountryId, ());
auto progress = static_cast<double>(mapSize.first) / mapSize.second;
auto interruptionProgress =
0.1 + 0.75 * static_cast<double>((i + j) % kInterruptionsCount) / kInterruptionsCount;
if (progress > interruptionProgress)
testing::StopEventLoop();
};
InitStorage(storage, onProgressFn);
storage.DownloadNode(kCountryId);
testing::RunEventLoop();
TEST(storage.IsDownloadInProgress(), ());
}
// Continue downloading.
coding::SHA1::Hash newHash;
{
Storage storage(COUNTRIES_FILE);
InitStorage(storage, [](CountryId const & countryId, LocalAndRemoteSize const & mapSize) {});
TEST(storage.IsDownloadInProgress(), ());
NodeAttrs attrs;
storage.GetNodeAttrs(kCountryId, attrs);
TEST_EQUAL(NodeStatus::Downloading, attrs.m_status, ());
storage.DownloadNode(kCountryId);
TEST(storage.IsDownloadInProgress(), ());
testing::RunEventLoop();
newHash = coding::SHA1::Calculate(mapPath);
}
// Check hashes.
TEST_EQUAL(mapHash, newHash, ());
}
}
#endif

View file

@ -0,0 +1,285 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "map/framework.hpp"
#include "platform/http_request.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/scoped_dir.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "storage/storage.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include <string>
namespace storage_group_download_tests
{
using namespace platform;
using namespace std;
using namespace storage;
CountryId const kGroupCountryId = "Venezuela";
CountriesSet const kLeafCountriesIds = {"Venezuela_North", "Venezuela_South"};
string GetMwmFilePath(string const & version, CountryId const & countryId)
{
return base::JoinPath(GetPlatform().WritableDir(), version, countryId + DATA_FILE_EXTENSION);
}
string GetMwmDownloadingFilePath(string const & version, CountryId const & countryId)
{
return base::JoinPath(GetPlatform().WritableDir(), version,
countryId + DATA_FILE_EXTENSION READY_FILE_EXTENSION DOWNLOADING_FILE_EXTENSION);
}
string GetMwmResumeFilePath(string const & version, CountryId const & countryId)
{
return base::JoinPath(GetPlatform().WritableDir(), version,
countryId + DATA_FILE_EXTENSION READY_FILE_EXTENSION RESUME_FILE_EXTENSION);
}
void DownloadGroup(Storage & storage, bool oneByOne)
{
Platform & platform = GetPlatform();
string const version = strings::to_string(storage.GetCurrentDataVersion());
// Get children nodes for the group node.
CountriesVec children;
// All nodes in subtree (including the root) for the group node.
storage.GetChildren(kGroupCountryId, children);
CountriesSet subTree;
storage.ForEachInSubtree(kGroupCountryId, [&subTree](CountryId const & descendantId, bool /* groupNode */)
{ subTree.insert(descendantId); });
CountriesSet changed;
auto onChangeCountryFn = [&](CountryId const & countryId)
{
TEST(subTree.find(countryId) != subTree.end(), (countryId));
changed.insert(countryId);
if (!storage.IsDownloadInProgress())
{
// Stop waiting when all chilren will be downloaded.
testing::StopEventLoop();
}
};
CountriesSet downloadedChecker;
auto onProgressFn = [&](CountryId const & countryId, downloader::Progress const & progress)
{
TEST(subTree.find(countryId) != subTree.end(), ());
if (progress.m_bytesDownloaded == progress.m_bytesTotal)
{
auto const res = downloadedChecker.insert(countryId);
TEST_EQUAL(res.second, true, ()); // Every child is downloaded only once.
}
};
int const subsrcibtionId = storage.Subscribe(onChangeCountryFn, onProgressFn);
// Check group node is not downloaded
CountriesVec downloaded, available;
storage.GetChildrenInGroups(storage.GetRootId(), downloaded, available);
TEST(downloaded.empty(), ());
// Check children nodes are not downloaded
storage.GetChildrenInGroups(kGroupCountryId, downloaded, available);
TEST(downloaded.empty(), ());
// Check status for the all children nodes is set to NotDownloaded.
MwmSize totalGroupSize = 0;
for (auto const & countryId : children)
{
TEST_EQUAL(Status::NotDownloaded, storage.CountryStatusEx(countryId), ());
NodeAttrs attrs;
storage.GetNodeAttrs(countryId, attrs);
TEST_EQUAL(NodeStatus::NotDownloaded, attrs.m_status, ());
TEST_GREATER(attrs.m_mwmSize, 0, ());
totalGroupSize += attrs.m_mwmSize;
}
// Check status for the group node is set to NotDownloaded.
NodeAttrs attrs;
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::NotDownloaded, attrs.m_status, ());
TEST_EQUAL(attrs.m_mwmSize, totalGroupSize, ());
attrs = NodeAttrs();
// Check there is no mwm or any other files for the children nodes.
for (auto const & countryId : children)
{
string const mwmFullPath = GetMwmFilePath(version, countryId);
string const downloadingFullPath = GetMwmDownloadingFilePath(version, countryId);
string const resumeFullPath = GetMwmResumeFilePath(version, countryId);
TEST(!platform.IsFileExistsByFullPath(mwmFullPath), ());
TEST(!platform.IsFileExistsByFullPath(downloadingFullPath), ());
TEST(!platform.IsFileExistsByFullPath(resumeFullPath), ());
}
// Download the group.
if (oneByOne)
for (auto const & countryId : children)
storage.DownloadNode(countryId);
else
storage.DownloadNode(kGroupCountryId);
// Wait for downloading of all children.
testing::RunEventLoop();
// Check if all nodes in the subtree have been downloaded and changed.
TEST_EQUAL(changed, subTree, ());
TEST_EQUAL(downloadedChecker, subTree, ());
// Check status for the group node is set to OnDisk.
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
// Check status for the all children nodes is set to OnDisk.
for (auto const & countryId : children)
{
TEST_EQUAL(Status::OnDisk, storage.CountryStatusEx(countryId), ());
NodeAttrs attrs;
storage.GetNodeAttrs(countryId, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
}
// Check there is only mwm files are present and no any other for the children nodes.
for (auto const & countryId : children)
{
string const mwmFullPath = GetMwmFilePath(version, countryId);
string const downloadingFullPath = GetMwmDownloadingFilePath(version, countryId);
string const resumeFullPath = GetMwmResumeFilePath(version, countryId);
TEST(platform.IsFileExistsByFullPath(mwmFullPath), ());
TEST(!platform.IsFileExistsByFullPath(downloadingFullPath), ());
TEST(!platform.IsFileExistsByFullPath(resumeFullPath), ());
}
// Check group is downloaded.
storage.GetChildrenInGroups(storage.GetRootId(), downloaded, available);
TEST_EQUAL(downloaded, CountriesVec({kGroupCountryId}), ());
// Check all group children are downloaded.
storage.GetChildrenInGroups(kGroupCountryId, downloaded, available);
TEST_EQUAL(CountriesSet(children.begin(), children.end()), CountriesSet(downloaded.begin(), downloaded.end()), ());
storage.Unsubscribe(subsrcibtionId);
}
void DeleteGroup(Storage & storage, bool oneByOne)
{
Platform & platform = GetPlatform();
string const version = strings::to_string(storage.GetCurrentDataVersion());
// Get children nodes for the group node.
CountriesVec v;
storage.GetChildren(kGroupCountryId, v);
CountriesSet const children(v.begin(), v.end());
v.clear();
// Check group node is downloaded.
CountriesVec downloaded, available;
storage.GetChildrenInGroups(storage.GetRootId(), downloaded, available);
TEST_EQUAL(downloaded, CountriesVec({kGroupCountryId}), ());
// Check children nodes are downloaded.
storage.GetChildrenInGroups(kGroupCountryId, downloaded, available);
TEST_EQUAL(children, CountriesSet(downloaded.begin(), downloaded.end()), ());
// Check there are mwm files for the children nodes.
for (auto const & countryId : children)
{
string const mwmFullPath = GetMwmFilePath(version, countryId);
TEST(platform.IsFileExistsByFullPath(mwmFullPath), ());
}
// Delete the group
if (oneByOne)
for (auto const & countryId : children)
storage.DeleteNode(countryId);
else
storage.DeleteNode(kGroupCountryId);
// Check state for the group node is set to NotDownloaded and NoError.
NodeAttrs attrs;
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::NotDownloaded, attrs.m_status, ());
// Check state for the all children nodes is set to NotDownloaded and NoError.
for (auto const & countryId : children)
{
TEST_EQUAL(Status::NotDownloaded, storage.CountryStatusEx(countryId), ());
NodeAttrs attrs;
storage.GetNodeAttrs(countryId, attrs);
TEST_EQUAL(NodeStatus::NotDownloaded, attrs.m_status, ());
}
// Check there are no mwm files for the children nodes.
for (auto const & countryId : children)
{
string const mwmFullPath = GetMwmFilePath(version, countryId);
TEST(!platform.IsFileExistsByFullPath(mwmFullPath), ());
}
// Check group is not downloaded.
storage.GetChildrenInGroups(storage.GetRootId(), downloaded, available);
TEST(downloaded.empty(), ());
// Check all children nodes are not downloaded.
storage.GetChildrenInGroups(kGroupCountryId, downloaded, available);
TEST(downloaded.empty(), ());
}
void TestDownloadDelete(bool downloadOneByOne, bool deleteOneByOne)
{
WritableDirChanger writableDirChanger(kMapTestDir);
Storage storage;
string const version = strings::to_string(storage.GetCurrentDataVersion());
auto onUpdatedFn = [&](CountryId const &, storage::LocalFilePtr const localCountryFile)
{
CountryId const countryId = localCountryFile->GetCountryName();
TEST(kLeafCountriesIds.find(countryId) != kLeafCountriesIds.end(), ());
};
storage.Init(onUpdatedFn, [](CountryId const &, storage::LocalFilePtr const) { return false; });
storage.RegisterAllLocalMaps();
storage.SetDownloadingServersForTesting({kTestWebServer});
tests_support::ScopedDir cleanupVersionDir(version);
// Check children for the kGroupCountryId
CountriesVec children;
storage.GetChildren(kGroupCountryId, children);
TEST_EQUAL(CountriesSet(children.begin(), children.end()), kLeafCountriesIds, ());
DownloadGroup(storage, downloadOneByOne);
DeleteGroup(storage, deleteOneByOne);
}
UNIT_TEST(SmallMwms_GroupDownloadDelete_Test1)
{
TestDownloadDelete(false, false);
}
UNIT_TEST(SmallMwms_GroupDownloadDelete_Test2)
{
TestDownloadDelete(false, true);
}
UNIT_TEST(SmallMwms_GroupDownloadDelete_Test3)
{
TestDownloadDelete(true, false);
}
UNIT_TEST(SmallMwms_GroupDownloadDelete_Test4)
{
TestDownloadDelete(true, true);
}
} // namespace storage_group_download_tests

View file

@ -0,0 +1,178 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "storage/storage.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/scoped_dir.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "base/file_name_utils.hpp"
#include "base/scope_guard.hpp"
#include "base/string_utils.hpp"
#include "base/thread.hpp"
#include <string>
namespace storage_http_tests
{
using namespace platform;
using namespace std;
using namespace storage;
string const kCountryId = "Trinidad and Tobago";
string const kDisputedCountryId1 = "Jerusalem";
string const kDisputedCountryId2 = "Crimea";
string const kDisputedCountryId3 = "Campo de Hielo Sur";
string const kUndisputedCountryId = "Argentina_Buenos Aires_North";
void Update(CountryId const &, LocalFilePtr const localCountryFile)
{
TEST_EQUAL(localCountryFile->GetCountryName(), kCountryId, ());
}
void UpdateWithoutChecks(CountryId const &, LocalFilePtr const /* localCountryFile */) {}
string const GetMwmFullPath(string const & countryId, string const & version)
{
return base::JoinPath(GetPlatform().WritableDir(), version, countryId + DATA_FILE_EXTENSION);
}
string const GetDownloadingFullPath(string const & countryId, string const & version)
{
return base::JoinPath(GetPlatform().WritableDir(), version,
kCountryId + DATA_FILE_EXTENSION READY_FILE_EXTENSION DOWNLOADING_FILE_EXTENSION);
}
string const GetResumeFullPath(string const & countryId, string const & version)
{
return base::JoinPath(GetPlatform().WritableDir(), version,
kCountryId + DATA_FILE_EXTENSION READY_FILE_EXTENSION RESUME_FILE_EXTENSION);
}
void InitStorage(Storage & storage, Storage::UpdateCallback const & didDownload,
Storage::ProgressFunction const & progress)
{
auto const changeCountryFunction = [&](CountryId const & /* countryId */)
{
if (!storage.IsDownloadInProgress())
{
// End wait for downloading complete.
testing::StopEventLoop();
}
};
storage.Init(didDownload, [](CountryId const &, LocalFilePtr const) { return false; });
storage.RegisterAllLocalMaps();
storage.Subscribe(changeCountryFunction, progress);
storage.SetDownloadingServersForTesting({kTestWebServer});
}
class StorageHttpTest
{
public:
StorageHttpTest()
: m_writableDirChanger(kMapTestDir)
, m_version(strings::to_string(m_storage.GetCurrentDataVersion()))
, m_cleanupVersionDir(m_version)
{}
protected:
WritableDirChanger const m_writableDirChanger;
Storage m_storage;
string const m_version;
tests_support::ScopedDir const m_cleanupVersionDir;
};
UNIT_CLASS_TEST(StorageHttpTest, StorageDownloadNodeAndDeleteNode)
{
auto const progressFunction = [this](CountryId const & countryId, downloader::Progress const & progress)
{
NodeAttrs nodeAttrs;
m_storage.GetNodeAttrs(countryId, nodeAttrs);
TEST_EQUAL(progress.m_bytesDownloaded, nodeAttrs.m_downloadingProgress.m_bytesDownloaded, (countryId));
TEST_EQUAL(progress.m_bytesTotal, nodeAttrs.m_downloadingProgress.m_bytesTotal, (countryId));
TEST_EQUAL(countryId, kCountryId, (countryId));
};
InitStorage(m_storage, Update, progressFunction);
string const mwmFullPath = GetMwmFullPath(kCountryId, m_version);
string const downloadingFullPath = GetDownloadingFullPath(kCountryId, m_version);
string const resumeFullPath = GetResumeFullPath(kCountryId, m_version);
// Downloading to an empty directory.
m_storage.DownloadNode(kCountryId);
// Wait for downloading complete.
testing::RunEventLoop();
Platform & platform = GetPlatform();
TEST(platform.IsFileExistsByFullPath(mwmFullPath), ());
TEST(!platform.IsFileExistsByFullPath(downloadingFullPath), ());
TEST(!platform.IsFileExistsByFullPath(resumeFullPath), ());
// Downloading to directory with Angola.mwm.
m_storage.DownloadNode(kCountryId);
TEST(platform.IsFileExistsByFullPath(mwmFullPath), ());
TEST(!platform.IsFileExistsByFullPath(downloadingFullPath), ());
TEST(!platform.IsFileExistsByFullPath(resumeFullPath), ());
m_storage.DeleteNode(kCountryId);
TEST(!platform.IsFileExistsByFullPath(mwmFullPath), ());
}
UNIT_CLASS_TEST(StorageHttpTest, StorageDownloadAndDeleteDisputedNode)
{
auto const progressFunction = [this](CountryId const & countryId, downloader::Progress const & progress)
{
NodeAttrs nodeAttrs;
m_storage.GetNodeAttrs(countryId, nodeAttrs);
TEST_EQUAL(progress.m_bytesDownloaded, nodeAttrs.m_downloadingProgress.m_bytesDownloaded, (countryId));
TEST_EQUAL(progress.m_bytesTotal, nodeAttrs.m_downloadingProgress.m_bytesTotal, (countryId));
};
InitStorage(m_storage, UpdateWithoutChecks, progressFunction);
string const mwmFullPath1 = GetMwmFullPath(kDisputedCountryId1, m_version);
string const mwmFullPath2 = GetMwmFullPath(kDisputedCountryId2, m_version);
string const mwmFullPath3 = GetMwmFullPath(kDisputedCountryId3, m_version);
string const mwmFullPathUndisputed = GetMwmFullPath(kUndisputedCountryId, m_version);
// Downloading to an empty directory.
m_storage.DownloadNode(kDisputedCountryId1);
m_storage.DownloadNode(kDisputedCountryId2);
m_storage.DownloadNode(kDisputedCountryId3);
m_storage.DownloadNode(kUndisputedCountryId);
// Wait for downloading complete.
testing::RunEventLoop();
Platform & platform = GetPlatform();
TEST(platform.IsFileExistsByFullPath(mwmFullPath1), ());
TEST(platform.IsFileExistsByFullPath(mwmFullPath2), ());
TEST(platform.IsFileExistsByFullPath(mwmFullPath3), ());
TEST(platform.IsFileExistsByFullPath(mwmFullPathUndisputed), ());
CountriesVec downloadedChildren;
CountriesVec availChildren;
m_storage.GetChildrenInGroups(m_storage.GetRootId(), downloadedChildren, availChildren);
CountriesVec const expectedDownloadedChildren = {"Argentina", kDisputedCountryId2, kDisputedCountryId1};
TEST_EQUAL(downloadedChildren, expectedDownloadedChildren, ());
TEST_EQUAL(availChildren.size(), 223, ());
m_storage.DeleteNode(kDisputedCountryId1);
m_storage.DeleteNode(kDisputedCountryId2);
m_storage.DeleteNode(kDisputedCountryId3);
m_storage.DeleteNode(kUndisputedCountryId);
TEST(!platform.IsFileExistsByFullPath(mwmFullPath1), ());
TEST(!platform.IsFileExistsByFullPath(mwmFullPath2), ());
TEST(!platform.IsFileExistsByFullPath(mwmFullPath3), ());
TEST(!platform.IsFileExistsByFullPath(mwmFullPathUndisputed), ());
}
} // namespace storage_http_tests

View file

@ -0,0 +1,190 @@
#include "testing/testing.hpp"
#include "storage/storage_integration_tests/test_defines.hpp"
#include "map/framework.hpp"
#include "platform/downloader_defines.hpp"
#include "platform/http_request.hpp"
#include "platform/local_country_file_utils.hpp"
#include "platform/platform.hpp"
#include "platform/platform_tests_support/writable_dir_changer.hpp"
#include "coding/internal/file_data.hpp"
#include "storage/storage.hpp"
#include "base/file_name_utils.hpp"
#include <memory>
#include <string>
namespace storage_update_tests
{
using namespace platform;
using namespace std;
using namespace storage;
static FrameworkParams const kFrameworkParams(false /* m_enableDiffs */);
string const kCountriesTxtFile = COUNTRIES_FILE;
string const kMwmVersion1 = "190830";
// size_t const kCountriesTxtFileSize1 = 420632;
string const kMwmVersion2 = "190910";
// size_t const kCountriesTxtFileSize2 = 420634;
string const kGroupCountryId = "Belarus";
bool DownloadFile(string const & url, string const & filePath, size_t fileSize)
{
using namespace downloader;
DownloadStatus httpStatus;
bool finished = false;
unique_ptr<HttpRequest> request(HttpRequest::GetFile({url}, filePath, fileSize, [&](HttpRequest & request)
{
DownloadStatus const s = request.GetStatus();
if (s != DownloadStatus::InProgress)
{
httpStatus = s;
finished = true;
testing::StopEventLoop();
}
}));
testing::RunEventLoop();
return httpStatus == DownloadStatus::Completed;
}
string GetCountriesTxtWebUrl(string const version)
{
return kTestWebServer + "direct/" + version + "/" + kCountriesTxtFile;
}
string GetCountriesTxtFilePath()
{
return base::JoinPath(GetPlatform().WritableDir(), kCountriesTxtFile);
}
string GetMwmFilePath(string const & version, CountryId const & countryId)
{
return base::JoinPath(GetPlatform().WritableDir(), version, countryId + DATA_FILE_EXTENSION);
}
/// @todo We don't have direct version links for now.
/// Also Framework f(kFrameworkParams) will fail here, @see SmallMwms_3levels_Test.
/*
UNIT_TEST(SmallMwms_Update_Test)
{
WritableDirChanger writableDirChanger(kMapTestDir);
Platform & platform = GetPlatform();
auto onProgressFn = [&](CountryId const &, downloader::Progress const &) {};
// Download countries.txt for version 1
TEST(DownloadFile(GetCountriesTxtWebUrl(kMwmVersion1), GetCountriesTxtFilePath(), kCountriesTxtFileSize1), ());
{
Framework f(kFrameworkParams);
auto & storage = f.GetStorage();
string const version = strings::to_string(storage.GetCurrentDataVersion());
TEST_EQUAL(version, kMwmVersion1, ());
auto onChangeCountryFn = [&](CountryId const & countryId) {
if (!storage.IsDownloadInProgress())
testing::StopEventLoop();
};
storage.Subscribe(onChangeCountryFn, onProgressFn);
storage.SetDownloadingServersForTesting({kTestWebServer});
CountriesVec children;
storage.GetChildren(kGroupCountryId, children);
// Download group
storage.DownloadNode(kGroupCountryId);
testing::RunEventLoop();
// Check group node status is OnDisk
NodeAttrs attrs;
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
// Check mwm files for version 1 are present
for (auto const & child : children)
{
string const mwmFullPathV1 = GetMwmFilePath(kMwmVersion1, child);
TEST(platform.IsFileExistsByFullPath(mwmFullPathV1), ());
}
}
// Replace countries.txt by version 2
TEST(base::DeleteFileX(GetCountriesTxtFilePath()), ());
TEST(DownloadFile(GetCountriesTxtWebUrl(kMwmVersion2), GetCountriesTxtFilePath(), kCountriesTxtFileSize2), ());
{
Framework f(kFrameworkParams);
auto & storage = f.GetStorage();
string const version = strings::to_string(storage.GetCurrentDataVersion());
TEST_EQUAL(version, kMwmVersion2, ());
auto onChangeCountryFn = [&](CountryId const & countryId) {
if (!storage.IsDownloadInProgress())
testing::StopEventLoop();
};
storage.Subscribe(onChangeCountryFn, onProgressFn);
storage.SetDownloadingServersForTesting({kTestWebServer});
CountriesVec children;
storage.GetChildren(kGroupCountryId, children);
// Check group node status is OnDiskOutOfDate
NodeAttrs attrs;
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::OnDiskOutOfDate, attrs.m_status, ());
// Check children node status is OnDiskOutOfDate
for (auto const & child : children)
{
NodeAttrs attrs;
storage.GetNodeAttrs(child, attrs);
TEST_EQUAL(NodeStatus::OnDiskOutOfDate, attrs.m_status, ());
}
// Check mwm files for version 1 are present
for (auto const & child : children)
{
string const mwmFullPathV1 = GetMwmFilePath(kMwmVersion1, child);
TEST(platform.IsFileExistsByFullPath(mwmFullPathV1), ());
}
// Download group, new version
storage.DownloadNode(kGroupCountryId);
testing::RunEventLoop();
// Check group node status is OnDisk
storage.GetNodeAttrs(kGroupCountryId, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
// Check children node status is OnDisk
for (auto const & child : children)
{
NodeAttrs attrs;
storage.GetNodeAttrs(child, attrs);
TEST_EQUAL(NodeStatus::OnDisk, attrs.m_status, ());
}
// Check mwm files for version 2 are present and not present for version 1
for (auto const & child : children)
{
string const mwmFullPathV1 = GetMwmFilePath(kMwmVersion1, child);
string const mwmFullPathV2 = GetMwmFilePath(kMwmVersion2, child);
TEST(platform.IsFileExistsByFullPath(mwmFullPathV2), ());
TEST(!platform.IsFileExistsByFullPath(mwmFullPathV1), ());
}
}
}
*/
} // namespace storage_update_tests

View file

@ -0,0 +1,7 @@
#include "storage/storage_integration_tests/test_defines.hpp"
namespace storage
{
std::string const kMapTestDir = "map-tests";
std::string const kTestWebServer = "https://cdn.comaps.app";
} // namespace storage

View file

@ -0,0 +1,9 @@
#pragma once
#include <string>
namespace storage
{
extern std::string const kMapTestDir;
extern std::string const kTestWebServer;
} // namespace storage

View file

@ -0,0 +1,27 @@
project(storage_tests)
set(SRC
countries_tests.cpp
country_info_getter_tests.cpp
country_name_getter_tests.cpp
downloader_tests.cpp
fake_map_files_downloader.cpp
fake_map_files_downloader.hpp
helpers.cpp
helpers.hpp
simple_tree_test.cpp
storage_tests.cpp
task_runner.cpp
task_runner.hpp
test_map_files_downloader.cpp
test_map_files_downloader.hpp
)
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT REQUIRE_SERVER)
target_link_libraries(${PROJECT_NAME}
platform_tests_support
generator_tests_support
storage
cppjansson
)

View file

@ -0,0 +1,14 @@
#include "testing/testing.hpp"
#include "platform/platform.hpp"
#include "coding/sha1.hpp"
#include "base/logging.hpp"
UNIT_TEST(CalculateWorldSHA)
{
auto const path = GetPlatform().ResourcesDir();
for (char const * country : {WORLD_FILE_NAME, WORLD_COASTS_FILE_NAME})
LOG(LINFO, (country, coding::SHA1::CalculateBase64(path + country + DATA_FILE_EXTENSION)));
}

View file

@ -0,0 +1,409 @@
#include "testing/benchmark.hpp"
#include "testing/testing.hpp"
#include "storage/storage_tests/helpers.hpp"
#include "storage/country.hpp"
#include "storage/country_decl.hpp"
#include "storage/country_info_getter.hpp"
#include "storage/storage.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/stats.hpp"
#include "base/timer.hpp"
#include <map>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
namespace country_info_getter_tests
{
using namespace storage;
using namespace std;
static double constexpr kRectCompareEpsilon = 1e-2;
bool IsEmptyName(map<string, CountryInfo> const & id2info, string const & id)
{
auto const it = id2info.find(id);
TEST(it != id2info.end(), ());
return it->second.m_name.empty();
}
// A helper class to sample random points from mwms uniformly.
class RandomPointGenerator
{
public:
explicit RandomPointGenerator(mt19937 & randomEngine, vector<m2::RegionD> const & regions)
: m_randomEngine(randomEngine)
, m_regions(regions)
{
CHECK(!m_regions.empty(), ());
vector<double> areas(m_regions.size());
for (size_t i = 0; i < m_regions.size(); ++i)
areas[i] = m_regions[i].CalculateArea();
m_distr = discrete_distribution<size_t>(areas.begin(), areas.end());
}
m2::PointD operator()()
{
auto const i = m_distr(m_randomEngine);
return m_regions[i].GetRandomPoint(m_randomEngine);
}
private:
mt19937 m_randomEngine;
vector<m2::RegionD> m_regions;
discrete_distribution<size_t> m_distr;
};
template <typename Cont>
Cont Flatten(vector<Cont> const & cs)
{
Cont res;
for (auto const & c : cs)
res.insert(res.end(), c.begin(), c.end());
return res;
}
UNIT_TEST(CountryInfoGetter_GetByPoint_Smoke)
{
auto const getter = CreateCountryInfoGetter();
CountryInfo info;
// Minsk
getter->GetRegionInfo(mercator::FromLatLon(53.9022651, 27.5618818), info);
TEST_EQUAL(info.m_name, "Belarus, Minsk Region", ());
getter->GetRegionInfo(mercator::FromLatLon(-6.4146288, -38.0098101), info);
TEST_EQUAL(info.m_name, "Brazil, Rio Grande do Norte", ());
getter->GetRegionInfo(mercator::FromLatLon(34.6509, 135.5018), info);
TEST_EQUAL(info.m_name, "Japan, Kinki Region_Osaka_Osaka", ());
}
UNIT_TEST(CountryInfoGetter_GetRegionsCountryIdByRect_Smoke)
{
auto const getter = CreateCountryInfoGetter();
m2::PointD const p = mercator::FromLatLon(52.537695, 32.203884);
// Single mwm.
m2::PointD const halfSize = m2::PointD(0.1, 0.1);
auto const countries = getter->GetRegionsCountryIdByRect(m2::RectD(p - halfSize, p + halfSize), false /* rough */);
TEST_EQUAL(countries, vector<storage::CountryId>{"Russia_Bryansk Oblast"}, ());
// Several countries.
m2::PointD const halfSize2 = m2::PointD(0.5, 0.5);
auto const countries2 = getter->GetRegionsCountryIdByRect(m2::RectD(p - halfSize2, p + halfSize2), false /* rough */);
auto const expected =
vector<storage::CountryId>{"Belarus_Homiel Region", "Russia_Bryansk Oblast", "Ukraine_Chernihiv Oblast"};
TEST_EQUAL(countries2, expected, ());
// No one found.
auto const countries3 = getter->GetRegionsCountryIdByRect(m2::RectD(-halfSize, halfSize), false /* rough */);
TEST_EQUAL(countries3, vector<storage::CountryId>{}, ());
// Inside mwm (rough).
auto const countries4 = getter->GetRegionsCountryIdByRect(m2::RectD(p - halfSize, p + halfSize), true /* rough */);
TEST_EQUAL(countries, vector<storage::CountryId>{"Russia_Bryansk Oblast"}, ());
// Several countries (rough).
auto const countries5 = getter->GetRegionsCountryIdByRect(m2::RectD(p - halfSize2, p + halfSize2), true /* rough */);
auto const expected2 = vector<storage::CountryId>{"Belarus_Homiel Region", "Belarus_Maglieu Region",
"Russia_Bryansk Oblast", "Ukraine_Chernihiv Oblast", "US_Alaska"};
TEST_EQUAL(countries5, expected2, ());
}
UNIT_TEST(CountryInfoGetter_ValidName_Smoke)
{
string buffer;
ReaderPtr<Reader>(GetPlatform().GetReader(COUNTRIES_FILE)).ReadAsString(buffer);
map<string, CountryInfo> id2info;
storage::LoadCountryFile2CountryInfo(buffer, id2info);
Storage storage;
TEST(!IsEmptyName(id2info, "Belgium_West Flanders"), ());
TEST(!IsEmptyName(id2info, "France_Ile-de-France_Paris"), ());
}
UNIT_TEST(CountryInfoGetter_SomeRects)
{
auto const getter = CreateCountryInfoGetter();
m2::RectD rects[3];
getter->CalcUSALimitRect(rects);
LOG(LINFO, ("USA Continental:", rects[0]));
LOG(LINFO, ("Alaska:", rects[1]));
LOG(LINFO, ("Hawaii:", rects[2]));
LOG(LINFO, ("Canada:", getter->CalcLimitRect("Canada_")));
}
UNIT_TEST(CountryInfoGetter_HitsInRadius)
{
auto const getter = CreateCountryInfoGetter();
CountriesVec results;
getter->GetRegionsCountryId(mercator::FromLatLon(56.1702, 28.1505), results);
TEST_EQUAL(results.size(), 3, ());
TEST(find(results.begin(), results.end(), "Belarus_Vitebsk Region") != results.end(), ());
TEST(find(results.begin(), results.end(), "Latvia") != results.end(), ());
TEST(find(results.begin(), results.end(), "Russia_Pskov Oblast") != results.end(), ());
}
UNIT_TEST(CountryInfoGetter_HitsOnLongLine)
{
auto const getter = CreateCountryInfoGetter();
CountriesVec results;
getter->GetRegionsCountryId(mercator::FromLatLon(62.2507, -102.0753), results);
TEST_EQUAL(results.size(), 2, ());
TEST(find(results.begin(), results.end(), "Canada_Northwest Territories_East") != results.end(), ());
TEST(find(results.begin(), results.end(), "Canada_Nunavut_South") != results.end(), ());
}
UNIT_TEST(CountryInfoGetter_HitsInTheMiddleOfNowhere)
{
auto const getter = CreateCountryInfoGetter();
CountriesVec results;
getter->GetRegionsCountryId(mercator::FromLatLon(62.2900, -103.9423), results);
TEST_EQUAL(results.size(), 1, ());
TEST(find(results.begin(), results.end(), "Canada_Northwest Territories_East") != results.end(), ());
}
UNIT_TEST(CountryInfoGetter_BorderRelations)
{
auto const getter = CreateCountryInfoGetter();
ms::LatLon labels[] = {
{42.4318876, -8.6431592}, {42.6075172, -8.4714942}, {42.3436415, -7.8674242},
{42.1968459, -7.6114105}, {43.0118437, -7.5565749}, {43.0462247, -7.4739921},
{43.3709703, -8.3959425}, {43.0565609, -8.5296941}, {27.0006968, 49.6532161},
};
for (auto const & ll : labels)
{
auto const region = getter->GetRegionCountryId(mercator::FromLatLon(ll));
LOG(LINFO, (region));
TEST(!region.empty(), (ll));
}
}
UNIT_TEST(CountryInfoGetter_GetLimitRectForLeafSingleMwm)
{
auto const getter = CreateCountryInfoGetter();
Storage storage;
m2::RectD const boundingBox = getter->GetLimitRectForLeaf("Angola");
m2::RectD const expectedBoundingBox = {9.205259 /* minX */, -18.34456 /* minY */, 24.08212 /* maxX */,
-4.393187 /* maxY */};
TEST(AlmostEqualAbs(boundingBox, expectedBoundingBox, kRectCompareEpsilon), ());
}
UNIT_TEST(CountryInfoGetter_RegionRects)
{
auto reader = CountryInfoReader::CreateCountryInfoReader(GetPlatform());
CHECK(reader != nullptr, ());
Storage storage;
auto const & countries = reader->GetCountries();
for (size_t i = 0; i < countries.size(); ++i)
{
vector<m2::RegionD> regions;
reader->LoadRegionsFromDisk(i, regions);
m2::RectD rect;
for (auto const & region : regions)
region.ForEachPoint([&](m2::PointD const & point) { rect.Add(point); });
TEST(AlmostEqualAbs(rect, countries[i].m_rect, kRectCompareEpsilon), (rect, countries[i].m_rect));
}
}
// This is a test for consistency between data/countries.txt and data/packed_polygons.bin.
UNIT_TEST(CountryInfoGetter_Countries_And_Polygons)
{
auto reader = CountryInfoReader::CreateCountryInfoReader(GetPlatform());
CHECK(reader != nullptr, ());
Storage storage;
double const kRectSize = 10;
auto const & countries = reader->GetCountries();
// Set is used here because disputed territories may occur as leaves several times.
set<CountryId> storageLeaves;
storage.ForEachCountry([&](Country const & country) { storageLeaves.insert(country.Name()); });
TEST_EQUAL(countries.size(), storageLeaves.size(), ());
for (size_t defId = 0; defId < countries.size(); ++defId)
{
auto const & countryDef = countries[defId];
TEST_GREATER(storageLeaves.count(countryDef.m_countryId), 0, (countryDef.m_countryId));
auto const & p = countryDef.m_rect.Center();
auto const rect = mercator::RectByCenterXYAndSizeInMeters(p.x, p.y, kRectSize, kRectSize);
auto vec = reader->GetRegionsCountryIdByRect(rect, false /* rough */);
for (auto const & countryId : vec)
{
// This call fails a CHECK if |countryId| is not found.
storage.GetCountryFile(countryId);
}
}
}
BENCHMARK_TEST(CountryInfoGetter_RegionsByRect)
{
auto reader = CountryInfoReader::CreateCountryInfoReader(GetPlatform());
CHECK(reader != nullptr, ());
Storage storage;
auto const & countryDefs = reader->GetCountries();
base::Timer timer;
double const kRectSize = 10;
mt19937 rng(0);
vector<vector<m2::RegionD>> allRegions;
allRegions.reserve(countryDefs.size());
for (size_t i = 0; i < countryDefs.size(); ++i)
{
vector<m2::RegionD> regions;
reader->LoadRegionsFromDisk(i, regions);
allRegions.emplace_back(std::move(regions));
}
size_t totalPoints = 0;
for (auto const & regs : allRegions)
for (auto const & reg : regs)
totalPoints += reg.Size();
LOG(LINFO, ("Total points:", totalPoints));
{
size_t const kNumIterations = 1000;
double const t0 = timer.ElapsedSeconds();
// Antarctica's rect is too large and skews the random point generation.
vector<vector<m2::RegionD>> regionsWithoutAnarctica;
for (size_t i = 0; i < allRegions.size(); ++i)
{
if (countryDefs[i].m_countryId == "Antarctica")
continue;
regionsWithoutAnarctica.emplace_back(allRegions[i]);
}
RandomPointGenerator pointGen(rng, Flatten(regionsWithoutAnarctica));
vector<m2::PointD> points;
for (size_t i = 0; i < kNumIterations; i++)
points.emplace_back(pointGen());
map<CountryId, int> hits;
for (auto const & pt : points)
{
auto const rect = mercator::RectByCenterXYAndSizeInMeters(pt.x, pt.y, kRectSize, kRectSize);
auto vec = reader->GetRegionsCountryIdByRect(rect, false /* rough */);
for (auto const & countryId : vec)
++hits[countryId];
}
double const t1 = timer.ElapsedSeconds();
LOG(LINFO, ("hits:", hits.size(), "/", countryDefs.size(), t1 - t0));
}
{
map<CountryId, vector<double>> timesByCountry;
map<CountryId, double> avgTimeByCountry;
size_t kNumPointsPerCountry = 1;
CountryId longest;
for (size_t countryDefId = 0; countryDefId < countryDefs.size(); ++countryDefId)
{
RandomPointGenerator pointGen(rng, allRegions[countryDefId]);
auto const & countryId = countryDefs[countryDefId].m_countryId;
vector<double> & times = timesByCountry[countryId];
times.resize(kNumPointsPerCountry);
for (size_t i = 0; i < times.size(); ++i)
{
auto const pt = pointGen();
auto const rect = mercator::RectByCenterXYAndSizeInMeters(pt.x, pt.y, kRectSize, kRectSize);
double const t0 = timer.ElapsedSeconds();
auto vec = reader->GetRegionsCountryIdByRect(rect, false /* rough */);
double const t1 = timer.ElapsedSeconds();
times[i] = t1 - t0;
}
avgTimeByCountry[countryId] = base::AverageStats<double>(times).GetAverage();
if (longest.empty() || avgTimeByCountry[longest] < avgTimeByCountry[countryId])
longest = countryId;
}
LOG(LINFO, ("Slowest country for CountryInfoGetter (random point)", longest, avgTimeByCountry[longest]));
}
{
map<CountryId, vector<double>> timesByCountry;
map<CountryId, double> avgTimeByCountry;
size_t kNumSidesPerCountry = 1;
CountryId longest;
for (size_t countryDefId = 0; countryDefId < countryDefs.size(); ++countryDefId)
{
auto const & countryId = countryDefs[countryDefId].m_countryId;
vector<pair<m2::PointD, m2::PointD>> sides;
for (auto const & region : allRegions[countryDefId])
{
auto const & points = region.Data();
for (size_t i = 0; i < points.size(); ++i)
sides.emplace_back(points[i], points[(i + 1) % points.size()]);
}
CHECK(!sides.empty(), ());
uniform_int_distribution<size_t> distr(0, sides.size() - 1);
vector<double> & times = timesByCountry[countryId];
times.resize(kNumSidesPerCountry);
for (size_t i = 0; i < times.size(); ++i)
{
auto const & side = sides[distr(rng)];
auto const pt = side.first.Mid(side.second);
auto const rect = mercator::RectByCenterXYAndSizeInMeters(pt.x, pt.y, kRectSize, kRectSize);
double const t0 = timer.ElapsedSeconds();
auto vec = reader->GetRegionsCountryIdByRect(rect, false /* rough */);
double const t1 = timer.ElapsedSeconds();
times[i] = t1 - t0;
}
avgTimeByCountry[countryId] = base::AverageStats<double>(times).GetAverage();
if (longest.empty() || avgTimeByCountry[longest] < avgTimeByCountry[countryId])
longest = countryId;
}
LOG(LINFO, ("Slowest country for CountryInfoGetter (point on a random side)", longest, avgTimeByCountry[longest]));
}
}
} // namespace country_info_getter_tests

View file

@ -0,0 +1,31 @@
#include "testing/testing.hpp"
#include "storage/country_name_getter.hpp"
#include <string>
using namespace std;
UNIT_TEST(CountryNameGetterTest)
{
string const shortJson =
"\
{\
\"Russia_Moscow\":\"Москва\",\
\"Russia_Rostov-on-Don\":\"Ростов-на-Дону\"\
}";
storage::CountryNameGetter getter;
TEST_EQUAL(string(), getter.GetLocale(), ());
TEST_EQUAL(string("Russia_Moscow"), getter("Russia_Moscow"), ());
TEST_EQUAL(string("Russia_Rostov-on-Don"), getter("Russia_Rostov-on-Don"), ());
TEST_EQUAL(string("Russia_Murmansk"), getter("Russia_Murmansk"), ());
getter.SetLocaleForTesting(shortJson, "ru");
TEST_EQUAL(string("ru"), getter.GetLocale(), ());
TEST_EQUAL(string("Москва"), getter("Russia_Moscow"), ());
TEST_EQUAL(string("Ростов-на-Дону"), getter("Russia_Rostov-on-Don"), ());
TEST_EQUAL(string("Russia_Murmansk"), getter("Russia_Murmansk"), ());
}

View file

@ -0,0 +1,33 @@
#include "testing/testing.hpp"
#include "storage/map_files_downloader_with_ping.hpp"
#include "base/logging.hpp"
#include "private.h"
namespace downloader_tests
{
class DownloaderStub : public storage::MapFilesDownloaderWithPing
{
virtual void Download(storage::QueuedCountry && queuedCountry) {}
};
UNIT_TEST(GetMetaConfig)
{
if (std::string(METASERVER_URL).empty())
return;
base::ScopedLogLevelChanger logLevel(base::LDEBUG);
Platform::ThreadRunner runner;
DownloaderStub().GetMetaConfig([](downloader::MetaConfig const & metaConfig)
{
TEST_GREATER(metaConfig.m_serversList.size(), 0, ());
for (auto const & s : metaConfig.m_serversList)
LOG(LINFO, (s));
});
}
} // namespace downloader_tests

View file

@ -0,0 +1,132 @@
#include "storage/storage_tests/fake_map_files_downloader.hpp"
#include "storage/storage_tests/task_runner.hpp"
#include "base/assert.hpp"
#include "base/scope_guard.hpp"
#include <algorithm>
#include <functional>
#include <utility>
namespace storage
{
int64_t const FakeMapFilesDownloader::kBlockSize;
FakeMapFilesDownloader::FakeMapFilesDownloader(TaskRunner & taskRunner) : m_timestamp(0), m_taskRunner(taskRunner)
{
SetServersList({"http://test-url/"});
}
FakeMapFilesDownloader::~FakeMapFilesDownloader()
{
CHECK_THREAD_CHECKER(m_checker, ());
}
void FakeMapFilesDownloader::Download(QueuedCountry && queuedCountry)
{
CHECK_THREAD_CHECKER(m_checker, ());
m_queue.Append(std::move(queuedCountry));
if (m_queue.Count() == 1)
Download();
}
void FakeMapFilesDownloader::Remove(CountryId const & id)
{
CHECK_THREAD_CHECKER(m_checker, ());
if (m_queue.IsEmpty())
return;
if (m_writer && m_queue.GetFirstId() == id)
m_writer.reset();
m_queue.Remove(id);
++m_timestamp;
}
void FakeMapFilesDownloader::Clear()
{
CHECK_THREAD_CHECKER(m_checker, ());
m_queue.Clear();
m_writer.reset();
++m_timestamp;
}
QueueInterface const & FakeMapFilesDownloader::GetQueue() const
{
CHECK_THREAD_CHECKER(m_checker, ());
return m_queue;
}
void FakeMapFilesDownloader::Download()
{
auto const & queuedCountry = m_queue.GetFirstCountry();
if (!IsDownloadingAllowed())
{
OnFileDownloaded(queuedCountry, downloader::DownloadStatus::Failed);
return;
}
queuedCountry.OnStartDownloading();
++m_timestamp;
m_progress = {};
m_progress.m_bytesTotal = queuedCountry.GetDownloadSize();
m_writer.reset(new FileWriter(queuedCountry.GetFileDownloadPath()));
m_taskRunner.PostTask(std::bind(&FakeMapFilesDownloader::DownloadNextChunk, this, m_timestamp));
}
void FakeMapFilesDownloader::DownloadNextChunk(uint64_t timestamp)
{
CHECK_THREAD_CHECKER(m_checker, ());
static std::string kZeroes(kBlockSize, '\0');
if (timestamp != m_timestamp)
return;
ASSERT_LESS_OR_EQUAL(m_progress.m_bytesDownloaded, m_progress.m_bytesTotal, ());
ASSERT(m_writer, ());
if (m_progress.m_bytesDownloaded == m_progress.m_bytesTotal)
{
OnFileDownloaded(m_queue.GetFirstCountry(), downloader::DownloadStatus::Completed);
return;
}
int64_t const bs = std::min(m_progress.m_bytesTotal - m_progress.m_bytesDownloaded, kBlockSize);
m_progress.m_bytesDownloaded += bs;
m_writer->Write(kZeroes.data(), bs);
m_writer->Flush();
m_taskRunner.PostTask([this, timestamp]()
{
CHECK_THREAD_CHECKER(m_checker, ());
if (timestamp != m_timestamp)
return;
m_queue.GetFirstCountry().OnDownloadProgress(m_progress);
});
m_taskRunner.PostTask(std::bind(&FakeMapFilesDownloader::DownloadNextChunk, this, timestamp));
}
void FakeMapFilesDownloader::OnFileDownloaded(QueuedCountry const & queuedCountry,
downloader::DownloadStatus const & status)
{
auto const country = queuedCountry;
m_queue.PopFront();
m_taskRunner.PostTask([country, status]() { country.OnDownloadFinished(status); });
if (!m_queue.IsEmpty())
Download();
}
} // namespace storage

View file

@ -0,0 +1,60 @@
#pragma once
#include "storage/downloader_queue_universal.hpp"
#include "storage/map_files_downloader.hpp"
#include "storage/queued_country.hpp"
#include "platform/downloader_defines.hpp"
#include "coding/file_writer.hpp"
#include "base/thread_checker.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace storage
{
class TaskRunner;
// This class can be used in tests to mimic a real downloader. It
// always returns a single URL for map files downloading and when
// asked for a file, creates a file with zero-bytes content on a disk.
// Because all callbacks must be invoked asynchronously, it needs a
// single-thread message loop runner to run callbacks.
//
// *NOTE*, this class is not thread-safe.
class FakeMapFilesDownloader : public MapFilesDownloader
{
public:
static int64_t const kBlockSize = 1024 * 1024;
FakeMapFilesDownloader(TaskRunner & taskRunner);
~FakeMapFilesDownloader();
// MapFilesDownloader overrides:
void Remove(CountryId const & id) override;
void Clear() override;
QueueInterface const & GetQueue() const override;
private:
// MapFilesDownloader overrides:
void Download(QueuedCountry && queuedCountry) override;
void Download();
void DownloadNextChunk(uint64_t requestId);
void OnFileDownloaded(QueuedCountry const & queuedCountry, downloader::DownloadStatus const & status);
downloader::Progress m_progress;
std::unique_ptr<FileWriter> m_writer;
uint64_t m_timestamp;
TaskRunner & m_taskRunner;
ThreadChecker m_checker;
Queue m_queue;
};
} // namespace storage

View file

@ -0,0 +1,17 @@
#include "testing/testing.hpp"
#include "storage/storage_tests/helpers.hpp"
#include "storage/country_info_getter.hpp"
#include "platform/platform.hpp"
#include <memory>
namespace storage
{
std::unique_ptr<storage::CountryInfoGetter> CreateCountryInfoGetter()
{
return CountryInfoReader::CreateCountryInfoGetter(GetPlatform());
}
} // namespace storage

View file

@ -0,0 +1,13 @@
#pragma once
#include "geometry/mercator.hpp"
#include <memory>
namespace storage
{
class CountryInfoGetter;
std::unique_ptr<CountryInfoGetter> CreateCountryInfoGetter();
} // namespace storage

View file

@ -0,0 +1,49 @@
#include "testing/testing.hpp"
#include "storage/country_tree.hpp"
UNIT_TEST(CountryTree_Smoke)
{
using Tree = storage::CountryTree::Node;
using Value = storage::Country;
Tree tree(Value("0"), nullptr);
tree.AddAtDepth(1, Value("4"));
tree.AddAtDepth(1, Value("3"));
tree.AddAtDepth(1, Value("5"));
tree.AddAtDepth(1, Value("2"));
tree.AddAtDepth(1, Value("1"));
tree.AddAtDepth(2, Value("20"));
tree.AddAtDepth(2, Value("10"));
tree.AddAtDepth(2, Value("30"));
// children test
TEST_EQUAL(tree.Child(0).Value().Name(), "4", ());
TEST_EQUAL(tree.Child(1).Value().Name(), "3", ());
TEST_EQUAL(tree.Child(2).Value().Name(), "5", ());
TEST_EQUAL(tree.Child(3).Value().Name(), "2", ());
TEST_EQUAL(tree.Child(4).Value().Name(), "1", ());
TEST_EQUAL(tree.Child(4).Child(0).Value().Name(), "20", ());
TEST_EQUAL(tree.Child(4).Child(1).Value().Name(), "10", ());
TEST_EQUAL(tree.Child(4).Child(2).Value().Name(), "30", ());
// parent test
TEST(!tree.HasParent(), ());
TEST(!tree.Child(0).Parent().HasParent(), ());
TEST_EQUAL(tree.Child(4).Child(0).Parent().Value().Name(), "1", ());
TEST_EQUAL(tree.Child(4).Child(2).Parent().Value().Name(), "1", ());
size_t count = 0;
auto countCallback = [&count](Tree const &) { ++count; };
tree.ForEachChild(countCallback);
TEST_EQUAL(count, 5, ());
count = 0;
tree.ForEachDescendant(countCallback);
TEST_EQUAL(count, 8, ());
count = 0;
tree.Child(4).Child(0).ForEachAncestorExceptForTheRoot(countCallback);
TEST_EQUAL(count, 1, ());
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
#include "storage/storage_tests/task_runner.hpp"
#include "base/assert.hpp"
namespace storage
{
TaskRunner::~TaskRunner()
{
CHECK(m_checker.CalledOnOriginalThread(), ());
}
void TaskRunner::Run()
{
CHECK(m_checker.CalledOnOriginalThread(), ());
while (!m_tasks.empty())
{
TTask task = m_tasks.front();
m_tasks.pop();
task();
}
}
void TaskRunner::PostTask(TTask const & task)
{
CHECK(m_checker.CalledOnOriginalThread(), ());
m_tasks.push(task);
}
} // namespace storage

View file

@ -0,0 +1,32 @@
#pragma once
#include "base/thread_checker.hpp"
#include <functional>
#include <queue>
namespace storage
{
// This class can be used in tests to mimic asynchronious calls. For
// example, when task A invokes task B and passes a callback C as an
// argument to B, it's silly for B to call C directly if B is an
// asynchronious task. So, the solution is to post C on the same
// message loop where B is run.
//
// *NOTE*, this class is not thread-safe.
class TaskRunner
{
public:
using TTask = std::function<void()>;
~TaskRunner();
void Run();
void PostTask(TTask const & task);
private:
std::queue<TTask> m_tasks;
ThreadChecker m_checker;
};
} // namespace storage

View file

@ -0,0 +1,9 @@
#include "storage/storage_tests/test_map_files_downloader.hpp"
namespace storage
{
TestMapFilesDownloader::TestMapFilesDownloader() : HttpMapFilesDownloader()
{
SetServersList({"http://localhost:34568/unit_tests/"});
}
} // namespace storage

View file

@ -0,0 +1,12 @@
#pragma once
#include "storage/http_map_files_downloader.hpp"
namespace storage
{
class TestMapFilesDownloader : public HttpMapFilesDownloader
{
public:
TestMapFilesDownloader();
};
} // namespace storage