Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
74
libs/storage/CMakeLists.txt
Normal file
74
libs/storage/CMakeLists.txt
Normal 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)
|
||||
28
libs/storage/background_downloading/downloader_adapter_ios.h
Normal file
28
libs/storage/background_downloading/downloader_adapter_ios.h
Normal 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
|
||||
137
libs/storage/background_downloading/downloader_adapter_ios.mm
Normal file
137
libs/storage/background_downloading/downloader_adapter_ios.mm
Normal 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
|
||||
81
libs/storage/background_downloading/downloader_queue.hpp
Normal file
81
libs/storage/background_downloading/downloader_queue.hpp
Normal 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
69
libs/storage/country.hpp
Normal 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
|
||||
36
libs/storage/country_decl.cpp
Normal file
36
libs/storage/country_decl.cpp
Normal 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
|
||||
66
libs/storage/country_decl.hpp
Normal file
66
libs/storage/country_decl.hpp
Normal 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
|
||||
356
libs/storage/country_info_getter.cpp
Normal file
356
libs/storage/country_info_getter.cpp
Normal 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
|
||||
180
libs/storage/country_info_getter.hpp
Normal file
180
libs/storage/country_info_getter.hpp
Normal 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
|
||||
81
libs/storage/country_info_reader_light.cpp
Normal file
81
libs/storage/country_info_reader_light.cpp
Normal 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
|
||||
42
libs/storage/country_info_reader_light.hpp
Normal file
42
libs/storage/country_info_reader_light.hpp
Normal 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
|
||||
43
libs/storage/country_name_getter.cpp
Normal file
43
libs/storage/country_name_getter.cpp
Normal 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
|
||||
42
libs/storage/country_name_getter.hpp
Normal file
42
libs/storage/country_name_getter.hpp
Normal 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
|
||||
17
libs/storage/country_parent_getter.cpp
Normal file
17
libs/storage/country_parent_getter.cpp
Normal 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
|
||||
21
libs/storage/country_parent_getter.hpp
Normal file
21
libs/storage/country_parent_getter.hpp
Normal 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
|
||||
493
libs/storage/country_tree.cpp
Normal file
493
libs/storage/country_tree.cpp
Normal 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
|
||||
130
libs/storage/country_tree.hpp
Normal file
130
libs/storage/country_tree.hpp
Normal 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
|
||||
61
libs/storage/country_tree_helpers.cpp
Normal file
61
libs/storage/country_tree_helpers.cpp
Normal 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
|
||||
17
libs/storage/country_tree_helpers.hpp
Normal file
17
libs/storage/country_tree_helpers.hpp
Normal 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
|
||||
68
libs/storage/diff_scheme/apply_diff.cpp
Normal file
68
libs/storage/diff_scheme/apply_diff.cpp
Normal 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
|
||||
29
libs/storage/diff_scheme/apply_diff.hpp
Normal file
29
libs/storage/diff_scheme/apply_diff.hpp
Normal 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
|
||||
148
libs/storage/diff_scheme/diff_scheme_loader.cpp
Normal file
148
libs/storage/diff_scheme/diff_scheme_loader.cpp
Normal 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
|
||||
30
libs/storage/diff_scheme/diff_scheme_loader.hpp
Normal file
30
libs/storage/diff_scheme/diff_scheme_loader.hpp
Normal 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
|
||||
31
libs/storage/diff_scheme/diff_types.hpp
Normal file
31
libs/storage/diff_scheme/diff_types.hpp
Normal 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
|
||||
107
libs/storage/diff_scheme/diffs_data_source.cpp
Normal file
107
libs/storage/diff_scheme/diffs_data_source.cpp
Normal 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
|
||||
65
libs/storage/diff_scheme/diffs_data_source.hpp
Normal file
65
libs/storage/diff_scheme/diffs_data_source.hpp
Normal 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
|
||||
10
libs/storage/downloader.hpp
Normal file
10
libs/storage/downloader.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class MapFilesDownloader;
|
||||
|
||||
std::unique_ptr<MapFilesDownloader> GetDownloader();
|
||||
} // namespace storage
|
||||
21
libs/storage/downloader_queue_interface.hpp
Normal file
21
libs/storage/downloader_queue_interface.hpp
Normal 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
|
||||
72
libs/storage/downloader_queue_universal.cpp
Normal file
72
libs/storage/downloader_queue_universal.cpp
Normal 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
|
||||
37
libs/storage/downloader_queue_universal.hpp
Normal file
37
libs/storage/downloader_queue_universal.hpp
Normal 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
|
||||
57
libs/storage/downloader_search_params.hpp
Normal file
57
libs/storage/downloader_search_params.hpp
Normal 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
|
||||
45
libs/storage/downloading_policy.cpp
Normal file
45
libs/storage/downloading_policy.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
40
libs/storage/downloading_policy.hpp
Normal file
40
libs/storage/downloading_policy.hpp
Normal 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;
|
||||
};
|
||||
150
libs/storage/http_map_files_downloader.cpp
Normal file
150
libs/storage/http_map_files_downloader.cpp
Normal 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
|
||||
45
libs/storage/http_map_files_downloader.hpp
Normal file
45
libs/storage/http_map_files_downloader.hpp
Normal 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
|
||||
193
libs/storage/map_files_downloader.cpp
Normal file
193
libs/storage/map_files_downloader.cpp
Normal 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
|
||||
113
libs/storage/map_files_downloader.hpp
Normal file
113
libs/storage/map_files_downloader.hpp
Normal 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
|
||||
27
libs/storage/map_files_downloader_with_ping.cpp
Normal file
27
libs/storage/map_files_downloader_with_ping.cpp
Normal 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
|
||||
13
libs/storage/map_files_downloader_with_ping.hpp
Normal file
13
libs/storage/map_files_downloader_with_ping.hpp
Normal 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
70
libs/storage/pinger.cpp
Normal 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
17
libs/storage/pinger.hpp
Normal 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
|
||||
107
libs/storage/queued_country.cpp
Normal file
107
libs/storage/queued_country.cpp
Normal 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
|
||||
62
libs/storage/queued_country.hpp
Normal file
62
libs/storage/queued_country.hpp
Normal 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
|
||||
27
libs/storage/routing_helpers.cpp
Normal file
27
libs/storage/routing_helpers.cpp
Normal 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
|
||||
17
libs/storage/routing_helpers.hpp
Normal file
17
libs/storage/routing_helpers.hpp
Normal 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
1963
libs/storage/storage.cpp
Normal file
File diff suppressed because it is too large
Load diff
733
libs/storage/storage.hpp
Normal file
733
libs/storage/storage.hpp
Normal 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
|
||||
90
libs/storage/storage_defines.cpp
Normal file
90
libs/storage/storage_defines.cpp
Normal 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
|
||||
92
libs/storage/storage_defines.hpp
Normal file
92
libs/storage/storage_defines.hpp
Normal 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 &)>;
|
||||
87
libs/storage/storage_helpers.cpp
Normal file
87
libs/storage/storage_helpers.cpp
Normal 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
|
||||
35
libs/storage/storage_helpers.hpp
Normal file
35
libs/storage/storage_helpers.hpp
Normal 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
|
||||
21
libs/storage/storage_integration_tests/CMakeLists.txt
Normal file
21
libs/storage/storage_integration_tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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(), ());
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
178
libs/storage/storage_integration_tests/storage_http_tests.cpp
Normal file
178
libs/storage/storage_integration_tests/storage_http_tests.cpp
Normal 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
|
||||
190
libs/storage/storage_integration_tests/storage_update_tests.cpp
Normal file
190
libs/storage/storage_integration_tests/storage_update_tests.cpp
Normal 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
|
||||
7
libs/storage/storage_integration_tests/test_defines.cpp
Normal file
7
libs/storage/storage_integration_tests/test_defines.cpp
Normal 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
|
||||
9
libs/storage/storage_integration_tests/test_defines.hpp
Normal file
9
libs/storage/storage_integration_tests/test_defines.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace storage
|
||||
{
|
||||
extern std::string const kMapTestDir;
|
||||
extern std::string const kTestWebServer;
|
||||
} // namespace storage
|
||||
27
libs/storage/storage_tests/CMakeLists.txt
Normal file
27
libs/storage/storage_tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
14
libs/storage/storage_tests/countries_tests.cpp
Normal file
14
libs/storage/storage_tests/countries_tests.cpp
Normal 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)));
|
||||
}
|
||||
409
libs/storage/storage_tests/country_info_getter_tests.cpp
Normal file
409
libs/storage/storage_tests/country_info_getter_tests.cpp
Normal 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
|
||||
31
libs/storage/storage_tests/country_name_getter_tests.cpp
Normal file
31
libs/storage/storage_tests/country_name_getter_tests.cpp
Normal 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"), ());
|
||||
}
|
||||
33
libs/storage/storage_tests/downloader_tests.cpp
Normal file
33
libs/storage/storage_tests/downloader_tests.cpp
Normal 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
|
||||
132
libs/storage/storage_tests/fake_map_files_downloader.cpp
Normal file
132
libs/storage/storage_tests/fake_map_files_downloader.cpp
Normal 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
|
||||
60
libs/storage/storage_tests/fake_map_files_downloader.hpp
Normal file
60
libs/storage/storage_tests/fake_map_files_downloader.hpp
Normal 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
|
||||
17
libs/storage/storage_tests/helpers.cpp
Normal file
17
libs/storage/storage_tests/helpers.cpp
Normal 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
|
||||
13
libs/storage/storage_tests/helpers.hpp
Normal file
13
libs/storage/storage_tests/helpers.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class CountryInfoGetter;
|
||||
|
||||
std::unique_ptr<CountryInfoGetter> CreateCountryInfoGetter();
|
||||
|
||||
} // namespace storage
|
||||
49
libs/storage/storage_tests/simple_tree_test.cpp
Normal file
49
libs/storage/storage_tests/simple_tree_test.cpp
Normal 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, ());
|
||||
}
|
||||
1488
libs/storage/storage_tests/storage_tests.cpp
Normal file
1488
libs/storage/storage_tests/storage_tests.cpp
Normal file
File diff suppressed because it is too large
Load diff
28
libs/storage/storage_tests/task_runner.cpp
Normal file
28
libs/storage/storage_tests/task_runner.cpp
Normal 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
|
||||
32
libs/storage/storage_tests/task_runner.hpp
Normal file
32
libs/storage/storage_tests/task_runner.hpp
Normal 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
|
||||
9
libs/storage/storage_tests/test_map_files_downloader.cpp
Normal file
9
libs/storage/storage_tests/test_map_files_downloader.cpp
Normal 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
|
||||
12
libs/storage/storage_tests/test_map_files_downloader.hpp
Normal file
12
libs/storage/storage_tests/test_map_files_downloader.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "storage/http_map_files_downloader.hpp"
|
||||
|
||||
namespace storage
|
||||
{
|
||||
class TestMapFilesDownloader : public HttpMapFilesDownloader
|
||||
{
|
||||
public:
|
||||
TestMapFilesDownloader();
|
||||
};
|
||||
} // namespace storage
|
||||
Loading…
Add table
Add a link
Reference in a new issue