[refactoring] network library #9600
|
@ -371,6 +371,7 @@ add_subdirectory(geometry)
|
|||
add_subdirectory(indexer)
|
||||
add_subdirectory(kml)
|
||||
add_subdirectory(map)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(cppjansson)
|
||||
add_subdirectory(platform)
|
||||
add_subdirectory(routing)
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
#include "storage/downloader.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
#include "platform/http_request.hpp"
|
||||
#include "network/http/request.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/internal/file_data.hpp"
|
||||
|
@ -24,7 +23,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace downloader;
|
||||
using namespace om::network;
|
||||
using namespace storage;
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
@ -46,13 +45,13 @@ namespace
|
|||
static std::vector<platform::CountryFile> g_filesToDownload;
|
||||
static int g_totalDownloadedBytes;
|
||||
static int g_totalBytesToDownload;
|
||||
static std::shared_ptr<HttpRequest> g_currentRequest;
|
||||
static std::shared_ptr<http::Request> g_currentRequest;
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
{
|
||||
using Callback = HttpRequest::Callback;
|
||||
using Callback = http::Request::Callback;
|
||||
|
||||
static int HasSpaceForFiles(Platform & pl, std::string const & sdcardPath, size_t fileSize)
|
||||
{
|
||||
|
@ -112,7 +111,7 @@ extern "C"
|
|||
return res;
|
||||
}
|
||||
|
||||
static void DownloadFileFinished(std::shared_ptr<jobject> obj, HttpRequest const & req)
|
||||
static void DownloadFileFinished(std::shared_ptr<jobject> obj, http::Request const & req)
|
||||
{
|
||||
auto const status = req.GetStatus();
|
||||
ASSERT_NOT_EQUAL(status, DownloadStatus::InProgress, ());
|
||||
|
@ -141,7 +140,7 @@ extern "C"
|
|||
env->CallVoidMethod(*obj, methodID, errorCode);
|
||||
}
|
||||
|
||||
static void DownloadFileProgress(std::shared_ptr<jobject> listener, HttpRequest const & req)
|
||||
static void DownloadFileProgress(std::shared_ptr<jobject> listener, http::Request const & req)
|
||||
{
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
static jmethodID methodID = jni::GetMethodID(env, *listener, "onProgress", "(I)V");
|
||||
|
@ -165,7 +164,7 @@ extern "C"
|
|||
auto const fileName = curFile.GetFileName(MapFileType::Map);
|
||||
LOG(LINFO, ("Downloading file", fileName));
|
||||
|
||||
g_currentRequest.reset(HttpRequest::GetFile(
|
||||
g_currentRequest.reset(http::Request::GetFile(
|
||||
downloader->MakeUrlListLegacy(fileName),
|
||||
storage.GetFilePath(curFile.GetName(), MapFileType::Map),
|
||||
curFile.GetRemoteSize(),
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
#include "platform/location.hpp"
|
||||
#include "platform/localization.hpp"
|
||||
#include "platform/measurement_utils.hpp"
|
||||
#include "platform/network_policy.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/preferred_languages.hpp"
|
||||
#include "platform/settings.hpp"
|
||||
|
@ -71,18 +70,9 @@ using namespace std::placeholders;
|
|||
|
||||
unique_ptr<android::Framework> g_framework;
|
||||
|
||||
namespace platform
|
||||
{
|
||||
NetworkPolicy ToNativeNetworkPolicy(JNIEnv * env, jobject obj)
|
||||
{
|
||||
return NetworkPolicy(network_policy::GetNetworkPolicyStatus(env, obj));
|
||||
}
|
||||
} // namespace platform
|
||||
|
||||
using namespace storage;
|
||||
using platform::CountryFile;
|
||||
using platform::LocalCountryFile;
|
||||
using platform::ToNativeNetworkPolicy;
|
||||
|
||||
static_assert(sizeof(int) >= 4, "Size of jint in less than 4 bytes.");
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
#include "base/thread_checker.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
#include "platform/downloader_utils.hpp"
|
||||
#include "network/progress.hpp"
|
||||
#include "network/downloader/utils.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/mwm_version.hpp"
|
||||
|
||||
|
@ -446,7 +446,7 @@ static void StatusChangedCallback(std::shared_ptr<jobject> const & listenerRef,
|
|||
}
|
||||
|
||||
static void ProgressChangedCallback(std::shared_ptr<jobject> const & listenerRef,
|
||||
storage::CountryId const & countryId, downloader::Progress const & progress)
|
||||
storage::CountryId const & countryId, om::network::Progress const & progress)
|
||||
{
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
|
||||
|
@ -533,7 +533,7 @@ Java_app_organicmaps_downloader_MapManager_nativeGetOverallProgress(JNIEnv * env
|
|||
countries.push_back(jni::ToNativeString(env, static_cast<jstring>(item.get())));
|
||||
}
|
||||
|
||||
downloader::Progress const progress = GetStorage().GetOverallProgress(countries);
|
||||
om::network::Progress const progress = GetStorage().GetOverallProgress(countries);
|
||||
|
||||
jint res = 0;
|
||||
if (progress.m_bytesTotal)
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
#include "search/mode.hpp"
|
||||
#include "search/result.hpp"
|
||||
|
||||
#include "platform/network_policy.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "app/organicmaps/util/NetworkPolicy.hpp"
|
||||
|
||||
#include "platform/network_policy.hpp"
|
||||
#include "network/network_policy.hpp"
|
||||
#include "platform/settings.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
@ -119,12 +119,12 @@ uint8_t Platform::GetBatteryLevel()
|
|||
return static_cast<uint8_t>(env->CallStaticIntMethod(clazzBatteryState, getLevelMethodId, context));
|
||||
}
|
||||
|
||||
namespace platform
|
||||
namespace om::network
|
||||
{
|
||||
platform::NetworkPolicy GetCurrentNetworkPolicy()
|
||||
NetworkPolicy GetCurrentNetworkPolicy()
|
||||
{
|
||||
JNIEnv *env = jni::GetEnv();
|
||||
return platform::NetworkPolicy(network_policy::GetCurrentNetworkUsageStatus(env));
|
||||
return NetworkPolicy(network_policy::GetCurrentNetworkUsageStatus(env));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,21 +2,20 @@
|
|||
#include "app/organicmaps/core/jni_helper.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "platform/http_thread_callback.hpp"
|
||||
#include "network/http/thread.hpp"
|
||||
#include "network/http/thread_callback.hpp"
|
||||
|
||||
class HttpThread
|
||||
namespace om::network::http
|
||||
{
|
||||
class Thread
|
||||
{
|
||||
private:
|
||||
jobject m_self;
|
||||
jclass m_klass;
|
||||
|
||||
public:
|
||||
HttpThread(std::string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t expectedFileSize,
|
||||
std::string const & pb)
|
||||
Thread(std::string const & url, om::network::http::IThreadCallback & cb, int64_t beg, int64_t end,
|
||||
int64_t expectedFileSize, std::string const & pb)
|
||||
{
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
static jclass const klass = jni::GetGlobalClassRef(env, "app/organicmaps/downloader/ChunkTask");
|
||||
|
@ -35,21 +34,16 @@ public:
|
|||
}
|
||||
|
||||
jni::TScopedLocalRef jUrl(env, jni::ToJavaString(env, url.c_str()));
|
||||
jni::TScopedLocalRef localSelf(env, env->NewObject(klass,
|
||||
initMethodId,
|
||||
reinterpret_cast<jlong>(&cb),
|
||||
jUrl.get(),
|
||||
static_cast<jlong>(beg),
|
||||
static_cast<jlong>(end),
|
||||
static_cast<jlong>(expectedFileSize),
|
||||
postBody.get()));
|
||||
jni::TScopedLocalRef localSelf(
|
||||
env, env->NewObject(klass, initMethodId, reinterpret_cast<jlong>(&cb), jUrl.get(), static_cast<jlong>(beg),
|
||||
static_cast<jlong>(end), static_cast<jlong>(expectedFileSize), postBody.get()));
|
||||
m_self = env->NewGlobalRef(localSelf.get());
|
||||
ASSERT(m_self, ());
|
||||
|
||||
env->CallVoidMethod(m_self, startMethodId);
|
||||
}
|
||||
|
||||
~HttpThread()
|
||||
~Thread()
|
||||
{
|
||||
JNIEnv * env = jni::GetEnv();
|
||||
static jmethodID const cancelMethodId = env->GetMethodID(m_klass, "cancel", "(Z)Z");
|
||||
|
@ -58,31 +52,25 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
namespace downloader
|
||||
namespace thread
|
||||
{
|
||||
HttpThread * CreateNativeHttpThread(std::string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t size,
|
||||
std::string const & pb)
|
||||
{
|
||||
return new HttpThread(url, cb, beg, end, size, pb);
|
||||
}
|
||||
Thread * CreateThread(std::string const & url, IThreadCallback & cb, int64_t beg, int64_t end, int64_t size,
|
||||
std::string const & pb)
|
||||
{
|
||||
return new Thread(url, cb, beg, end, size, pb);
|
||||
}
|
||||
|
||||
void DeleteNativeHttpThread(HttpThread * request)
|
||||
{
|
||||
delete request;
|
||||
}
|
||||
void DeleteThread(Thread * request) { delete request; }
|
||||
|
||||
} // namespace downloader
|
||||
} // namespace thread
|
||||
} // namespace om::network::http
|
||||
|
||||
extern "C"
|
||||
{
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_app_organicmaps_downloader_ChunkTask_nativeOnWrite(JNIEnv * env, jclass clazz, jlong httpCallbackID, jlong beg, jbyteArray data, jlong size)
|
||||
{
|
||||
downloader::IHttpThreadCallback * cb = reinterpret_cast<downloader::IHttpThreadCallback*>(httpCallbackID);
|
||||
om::network::http::IThreadCallback * cb = reinterpret_cast<om::network::http::IThreadCallback*>(httpCallbackID);
|
||||
jbyte * buf = env->GetByteArrayElements(data, 0);
|
||||
ASSERT(buf, ());
|
||||
|
||||
|
@ -103,7 +91,7 @@ Java_app_organicmaps_downloader_ChunkTask_nativeOnWrite(JNIEnv * env, jclass cla
|
|||
JNIEXPORT void JNICALL
|
||||
Java_app_organicmaps_downloader_ChunkTask_nativeOnFinish(JNIEnv * env, jclass clazz, jlong httpCallbackID, jlong httpCode, jlong beg, jlong end)
|
||||
{
|
||||
downloader::IHttpThreadCallback * cb = reinterpret_cast<downloader::IHttpThreadCallback*>(httpCallbackID);
|
||||
om::network::http::IThreadCallback * cb = reinterpret_cast<om::network::http::IThreadCallback*>(httpCallbackID);
|
||||
cb->OnFinish(static_cast<long>(httpCode), beg, end);
|
||||
}
|
||||
} // extern "C"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include "app/organicmaps/core/jni_helper.hpp"
|
||||
|
||||
#include "platform/socket.hpp"
|
||||
#include "network/socket.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace platform
|
||||
namespace om::network
|
||||
{
|
||||
class SocketImpl : public Socket
|
||||
{
|
||||
|
|
|
@ -27,7 +27,7 @@ SOFTWARE.
|
|||
#include "app/organicmaps/core/ScopedEnv.hpp"
|
||||
#include "app/organicmaps/core/ScopedLocalRef.hpp"
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/exception.hpp"
|
||||
|
@ -98,7 +98,7 @@ void GetInt(ScopedEnv & env, jobject const params, jfieldID const fieldId, int &
|
|||
RethrowOnJniException(env);
|
||||
}
|
||||
|
||||
void SetHeaders(ScopedEnv & env, jobject const params, platform::HttpClient::Headers const & headers)
|
||||
void SetHeaders(ScopedEnv & env, jobject const params, om::network::http::Client::Headers const & headers)
|
||||
{
|
||||
if (headers.empty())
|
||||
return;
|
||||
|
@ -113,7 +113,7 @@ void SetHeaders(ScopedEnv & env, jobject const params, platform::HttpClient::Hea
|
|||
RethrowOnJniException(env);
|
||||
}
|
||||
|
||||
void LoadHeaders(ScopedEnv & env, jobject const params, platform::HttpClient::Headers & headers)
|
||||
void LoadHeaders(ScopedEnv & env, jobject const params, om::network::http::Client::Headers & headers)
|
||||
{
|
||||
static jmethodID const getHeaders =
|
||||
env->GetMethodID(g_httpParamsClazz, "getHeaders", "()[Ljava/lang/Object;");
|
||||
|
@ -161,9 +161,9 @@ private:
|
|||
//***********************************************************************
|
||||
// Exported functions implementation
|
||||
//***********************************************************************
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
bool HttpClient::RunHttpRequest()
|
||||
bool Client::RunHttpRequest()
|
||||
{
|
||||
ScopedEnv env(jni::GetJVM());
|
||||
|
||||
|
@ -262,4 +262,4 @@ bool HttpClient::RunHttpRequest()
|
|||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace platform
|
||||
} // namespace om::network::http
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace base
|
||||
{
|
||||
// Class for multithreaded interruptable waiting.
|
||||
|
|
|
@ -27,19 +27,20 @@ set_tests_properties(OmimStartTestServer OmimStopTestServer PROPERTIES LABELS "o
|
|||
# * REQUIRE_SERVER - requires test server (TestServer fixture that runs testserver.py)
|
||||
# * NO_PLATFORM_INIT - test doesn't require platform dependencies
|
||||
# * BOOST_TEST - test is written with Boost.Test
|
||||
# * GTEST - test is written with GoogleTest
|
||||
function(omim_add_test name)
|
||||
if (SKIP_TESTS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(options REQUIRE_QT REQUIRE_SERVER NO_PLATFORM_INIT BOOST_TEST)
|
||||
set(options REQUIRE_QT REQUIRE_SERVER NO_PLATFORM_INIT BOOST_TEST GTEST)
|
||||
cmake_parse_arguments(TEST "${options}" "" "" ${ARGN})
|
||||
|
||||
set(TEST_NAME ${name})
|
||||
set(TEST_SRC ${TEST_UNPARSED_ARGUMENTS})
|
||||
|
||||
omim_add_test_target(${TEST_NAME} "${TEST_SRC}" ${TEST_NO_PLATFORM_INIT} ${TEST_REQUIRE_QT} ${TEST_BOOST_TEST})
|
||||
omim_add_ctest(${TEST_NAME} ${TEST_REQUIRE_SERVER} ${TEST_BOOST_TEST})
|
||||
omim_add_test_target(${TEST_NAME} "${TEST_SRC}" ${TEST_NO_PLATFORM_INIT} ${TEST_REQUIRE_QT} ${TEST_BOOST_TEST} ${TEST_GTEST})
|
||||
omim_add_ctest(${TEST_NAME} ${TEST_REQUIRE_SERVER} ${TEST_BOOST_TEST} ${TEST_GTEST})
|
||||
endfunction()
|
||||
|
||||
function(omim_add_test_subdirectory subdir)
|
||||
|
@ -50,11 +51,12 @@ function(omim_add_test_subdirectory subdir)
|
|||
endif()
|
||||
endfunction()
|
||||
|
||||
function(omim_add_test_target name src no_platform_init require_qt boost_test)
|
||||
omim_add_executable(${name}
|
||||
${src}
|
||||
$<$<NOT:$<BOOL:${boost_test}>>:${OMIM_ROOT}/testing/testingmain.cpp>
|
||||
)
|
||||
function(omim_add_test_target name src no_platform_init require_qt boost_test gtest)
|
||||
omim_add_executable(${name} ${src})
|
||||
if(NOT ${boost_test} AND NOT ${gtest})
|
||||
target_sources(${name} PRIVATE ${OMIM_ROOT}/testing/testingmain.cpp)
|
||||
endif()
|
||||
|
||||
target_compile_options(${name} PRIVATE ${OMIM_WARNING_FLAGS})
|
||||
target_include_directories(${name} PRIVATE ${OMIM_INCLUDE_DIRS})
|
||||
|
||||
|
@ -69,14 +71,14 @@ function(omim_add_test_target name src no_platform_init require_qt boost_test)
|
|||
target_link_libraries(${name} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
if (NOT boost_test)
|
||||
if (NOT boost_test AND NOT gtest)
|
||||
# testingmain.cpp uses base::HighResTimer::ElapsedNano
|
||||
target_link_libraries(${name} base)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(omim_add_ctest name require_server boost_test)
|
||||
if (NOT boost_test)
|
||||
function(omim_add_ctest name require_server boost_test gtest)
|
||||
if (NOT boost_test AND NOT gtest)
|
||||
set(test_command ${name} --data_path=${OMIM_DATA_DIR} --user_resource_path=${OMIM_USER_RESOURCES_DIR})
|
||||
else()
|
||||
set(test_command ${name})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "editor/osm_auth.hpp"
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
#include "coding/url.hpp"
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
|||
|
||||
namespace osm
|
||||
{
|
||||
using platform::HttpClient;
|
||||
using namespace om::network;
|
||||
using std::string;
|
||||
|
||||
constexpr char const * kApiVersion = "/api/0.6";
|
||||
|
@ -131,7 +131,7 @@ bool OsmOAuth::IsAuthorized() const{ return IsValid(m_oauth2token); }
|
|||
OsmOAuth::SessionID OsmOAuth::FetchSessionId(string const & subUrl, string const & cookies) const
|
||||
{
|
||||
string const url = m_baseUrl + subUrl + (cookies.empty() ? "?cookie_test=true" : "");
|
||||
HttpClient request(url);
|
||||
http::Client request(url);
|
||||
request.SetCookies(cookies);
|
||||
if (!request.RunHttpRequest())
|
||||
MYTHROW(NetworkError, ("FetchSessionId Network error while connecting to", url));
|
||||
|
@ -148,7 +148,7 @@ OsmOAuth::SessionID OsmOAuth::FetchSessionId(string const & subUrl, string const
|
|||
|
||||
void OsmOAuth::LogoutUser(SessionID const & sid) const
|
||||
{
|
||||
HttpClient request(m_baseUrl + "/logout");
|
||||
http::Client request(m_baseUrl + "/logout");
|
||||
request.SetCookies(sid.m_cookies);
|
||||
if (!request.RunHttpRequest())
|
||||
MYTHROW(NetworkError, ("LogoutUser Network error while connecting to", request.UrlRequested()));
|
||||
|
@ -165,7 +165,7 @@ bool OsmOAuth::LoginUserPassword(string const & login, string const & password,
|
|||
{"commit", "Login"},
|
||||
{"authenticity_token", sid.m_authenticityToken}
|
||||
});
|
||||
HttpClient request(m_baseUrl + "/login");
|
||||
http::Client request(m_baseUrl + "/login");
|
||||
request.SetBodyData(std::move(params), "application/x-www-form-urlencoded")
|
||||
.SetCookies(sid.m_cookies)
|
||||
.SetFollowRedirects(true);
|
||||
|
@ -192,7 +192,7 @@ bool OsmOAuth::LoginUserPassword(string const & login, string const & password,
|
|||
bool OsmOAuth::LoginSocial(string const & callbackPart, string const & socialToken, SessionID const & sid) const
|
||||
{
|
||||
string const url = m_baseUrl + callbackPart + socialToken;
|
||||
HttpClient request(url);
|
||||
http::Client request(url);
|
||||
request.SetCookies(sid.m_cookies)
|
||||
.SetFollowRedirects(true);
|
||||
if (!request.RunHttpRequest())
|
||||
|
@ -222,7 +222,7 @@ string OsmOAuth::SendAuthRequest(string const & requestTokenKey, SessionID const
|
|||
{"scope", m_oauth2params.m_scope},
|
||||
{"response_type", "code"}
|
||||
});
|
||||
HttpClient request(m_baseUrl + "/oauth2/authorize");
|
||||
http::Client request(m_baseUrl + "/oauth2/authorize");
|
||||
request.SetBodyData(std::move(params), "application/x-www-form-urlencoded")
|
||||
.SetCookies(lastSid.m_cookies)
|
||||
//.SetRawHeader("Origin", m_baseUrl)
|
||||
|
@ -241,7 +241,7 @@ string OsmOAuth::SendAuthRequest(string const & requestTokenKey, SessionID const
|
|||
|
||||
string OsmOAuth::FetchRequestToken(SessionID const & sid) const
|
||||
{
|
||||
HttpClient request(BuildOAuth2Url());
|
||||
http::Client request(BuildOAuth2Url());
|
||||
request.SetCookies(sid.m_cookies)
|
||||
.SetFollowRedirects(false);
|
||||
|
||||
|
@ -297,7 +297,7 @@ string OsmOAuth::FinishAuthorization(string const & oauth2code) const
|
|||
{"scope", m_oauth2params.m_scope},
|
||||
});
|
||||
|
||||
HttpClient request(m_baseUrl + "/oauth2/token");
|
||||
http::Client request(m_baseUrl + "/oauth2/token");
|
||||
request.SetBodyData(std::move(params), "application/x-www-form-urlencoded")
|
||||
.SetFollowRedirects(true);
|
||||
if (!request.RunHttpRequest())
|
||||
|
@ -343,7 +343,7 @@ bool OsmOAuth::ResetPassword(string const & email) const
|
|||
{"authenticity_token", sid.m_authenticityToken},
|
||||
{"commit", "Reset password"},
|
||||
});
|
||||
HttpClient request(m_baseUrl + kForgotPasswordUrlPart);
|
||||
http::Client request(m_baseUrl + kForgotPasswordUrlPart);
|
||||
request.SetBodyData(std::move(params), "application/x-www-form-urlencoded");
|
||||
request.SetCookies(sid.m_cookies);
|
||||
request.SetHandleRedirects(false);
|
||||
|
@ -366,7 +366,7 @@ OsmOAuth::Response OsmOAuth::Request(string const & method, string const & httpM
|
|||
|
||||
string url = m_apiUrl + kApiVersion + method;
|
||||
|
||||
HttpClient request(url);
|
||||
http::Client request(url);
|
||||
request.SetRawHeader("Authorization", "Bearer " + m_oauth2token);
|
||||
|
||||
if (httpMethod != "GET")
|
||||
|
@ -382,7 +382,7 @@ OsmOAuth::Response OsmOAuth::Request(string const & method, string const & httpM
|
|||
OsmOAuth::Response OsmOAuth::DirectRequest(string const & method, bool api) const
|
||||
{
|
||||
string const url = api ? m_apiUrl + kApiVersion + method : m_baseUrl + method;
|
||||
HttpClient request(url);
|
||||
http::Client request(url);
|
||||
if (!request.RunHttpRequest())
|
||||
MYTHROW(NetworkError, ("DirectRequest Network error while connecting to", url));
|
||||
if (request.WasRedirected())
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "base/sunrise_sunset.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/network_policy_ios.h"
|
||||
|
||||
@implementation MWMFrameworkHelper
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#import "MWMNetworkPolicy.h"
|
||||
|
||||
#include "platform/network_policy_ios.h"
|
||||
#include "network/internal/native/apple/network_policy.h"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
using namespace om::network;
|
||||
|
||||
@implementation MWMNetworkPolicy
|
||||
|
||||
+ (MWMNetworkPolicy *)sharedPolicy {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#import "MWMMapNodeAttributes.h"
|
||||
|
||||
#include <CoreApi/CoreApi.h>
|
||||
#include "platform/network_policy.hpp"
|
||||
|
||||
static place_page::Info & rawData() { return GetFramework().GetCurrentPlacePageInfo(); }
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ using namespace storage;
|
|||
[observer processCountryEvent:@(countryId.c_str())];
|
||||
}
|
||||
},
|
||||
[observers](CountryId const & countryId, downloader::Progress const & progress) {
|
||||
[observers](CountryId const & countryId, om::network::Progress const & progress) {
|
||||
for (id<MWMStorageObserver> observer in observers) {
|
||||
// processCountry function in observer's implementation may not exist.
|
||||
/// @todo We can face with an invisible bug, if function's signature will be changed.
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
NSString * const kDownloadTransitMapAlertNibName = @"MWMDownloadTransitMapAlert";
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
|
||||
#include "map/gps_tracker.hpp"
|
||||
|
||||
#include "platform/background_downloader_ios.h"
|
||||
#include "platform/http_thread_apple.h"
|
||||
#include "network/internal/native/apple/http/thread.h"
|
||||
#include "network/internal/native/apple/background_downloader.h"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/network_policy.hpp"
|
||||
#include "platform/preferred_languages.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
using Observer = id<MWMFrameworkObserver>;
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include "storage/storage.hpp"
|
||||
#include "storage/storage_defines.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
using namespace storage;
|
||||
|
||||
@protocol MWMFrameworkRouteBuilderObserver<MWMFrameworkObserver>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
#include "platform/network_policy.hpp"
|
||||
|
||||
namespace {
|
||||
using Observer = id<MWMSearchObserver>;
|
||||
using Observers = NSHashTable<Observer>;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
039371B62C5B68CD00708377 /* UIFont+monospaced.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039371B52C5B68CD00708377 /* UIFont+monospaced.swift */; };
|
||||
167B3F5B2CD567400052F24F /* libnetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 167B3F5A2CD567400052F24F /* libnetwork.a */; };
|
||||
167B3F862CD5680B0052F24F /* libbase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 167B3F852CD5680B0052F24F /* libbase.a */; };
|
||||
1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA2F6920D3B57400FB2C66 /* UIColor+PartnerColor.m */; };
|
||||
3304306D21D4EAFB00317CA3 /* SearchCategoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3304306C21D4EAFB00317CA3 /* SearchCategoryCell.swift */; };
|
||||
33046832219C57180041F3A8 /* CategorySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33046831219C57180041F3A8 /* CategorySettingsViewController.swift */; };
|
||||
|
@ -747,6 +749,8 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
039371B52C5B68CD00708377 /* UIFont+monospaced.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+monospaced.swift"; sourceTree = "<group>"; };
|
||||
167B3F5A2CD567400052F24F /* libnetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libnetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
167B3F852CD5680B0052F24F /* libbase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libbase.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1D3623240D0F684500981E51 /* MapsAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MapsAppDelegate.h; sourceTree = "<group>"; };
|
||||
1D3623250D0F684500981E51 /* MapsAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = MapsAppDelegate.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
1DFA2F6820D3B52F00FB2C66 /* UIColor+PartnerColor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+PartnerColor.h"; sourceTree = "<group>"; };
|
||||
|
@ -1758,6 +1762,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
167B3F862CD5680B0052F24F /* libbase.a in Frameworks */,
|
||||
FAF9DDA32A86DC54000D7037 /* libharfbuzz.a in Frameworks */,
|
||||
FA456C3C26BDC6AD00B83C20 /* Chart.framework in Frameworks */,
|
||||
FA853BF326BC5DE50026D455 /* libshaders.a in Frameworks */,
|
||||
|
@ -1797,6 +1802,7 @@
|
|||
FA853BD126BC3B8A0026D455 /* libsuccinct.a in Frameworks */,
|
||||
FA853BA726BC3ACE0026D455 /* CoreApi.framework in Frameworks */,
|
||||
FA14E686276014C10066E453 /* libz.1.tbd in Frameworks */,
|
||||
167B3F5B2CD567400052F24F /* libnetwork.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1903,6 +1909,8 @@
|
|||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
167B3F852CD5680B0052F24F /* libbase.a */,
|
||||
167B3F5A2CD567400052F24F /* libnetwork.a */,
|
||||
FAF9DDA22A86DC54000D7037 /* libharfbuzz.a */,
|
||||
FA456C3B26BDC6AD00B83C20 /* Chart.framework */,
|
||||
FA853BF226BC5DE50026D455 /* libshaders.a */,
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#import "MWMStorage+UI.h"
|
||||
#import "SwiftBridge.h"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#import <CoreApi/Framework.h>
|
||||
#import <CoreApi/StringUtils.h>
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
#include "indexer/validate_and_format_contacts.hpp"
|
||||
|
||||
using namespace storage;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
#include "platform/http_client.hpp"
|
||||
#include "network/progress.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
|
@ -195,7 +195,7 @@ void RunGraphicsBenchmark(Framework * framework)
|
|||
}
|
||||
}
|
||||
},
|
||||
[](storage::CountryId const &, downloader::Progress const &) {});
|
||||
[](storage::CountryId const &, om::network::Progress const &) {});
|
||||
|
||||
for (auto const & countryId : handle->m_regionsToDownload)
|
||||
framework->GetStorage().DownloadNode(countryId);
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp" // kPointEqualityEps
|
||||
#include "geometry/simplification.hpp"
|
||||
|
|
76
network/CMakeLists.txt
Normal file
|
@ -0,0 +1,76 @@
|
|||
project(network)
|
||||
|
||||
set(PUBLIC_SOURCES
|
||||
downloader/utils.cpp
|
||||
downloader/utils.hpp
|
||||
http/background_uploader.hpp
|
||||
http/client.cpp
|
||||
http/client.hpp
|
||||
http/payload.hpp
|
||||
http/request.cpp
|
||||
http/request.hpp
|
||||
http/thread.hpp
|
||||
http/thread_callback.hpp
|
||||
http/uploader.hpp
|
||||
chunks_download_strategy.cpp
|
||||
chunks_download_strategy.hpp
|
||||
download_status.hpp
|
||||
non_http_error_code.hpp
|
||||
progress.hpp
|
||||
servers_list.cpp
|
||||
servers_list.hpp
|
||||
socket.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${PUBLIC_SOURCES})
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
internal/http/file_request.hpp
|
||||
AndrewShkrob
commented
An internal folder was added on purpose. An internal folder was added on purpose.
As I wrote in the description, Internal folder will hide all sources (and headers) that shouldn't be visible outside of the library.
This will help to organize our sources and keep the public interface clear.
In theory (it can be tested) it may also reduce the binary size or at least linking stage time
![]() With your proposed change, developers immediately see header files that are part of the public interface. However, it may be harder to start using something necessary that was hidden before in the internal folder. In this specific case, IMO it is easier to keep "internal" files in their subfolders ("http"), the "internal" folder adds an unnecessary level to the sources tree. With your proposed change, developers immediately see header files that are part of the public interface. However, it may be harder to start using something necessary that was hidden before in the internal folder.
In this specific case, IMO it is easier to keep "internal" files in their subfolders ("http"), the "internal" folder adds an unnecessary level to the sources tree.
AndrewShkrob
commented
I can propose another file structure:
But anyway, for a library there should be
The only reason it may be harder (regarding our project) is that the feature is not yet implemented for every platform. We have this problem right now. Some files/classes are used in the iOS project, but they are not implemented for Android. So, there is no single public interface for it. All in all, I think the problem you described is not a real problem :)
P.S. I just reviewed these two file structures and must admit I like the second one more (with separate @biodranik @vng WDYT? I can propose another file structure:
```
├── include
│ ├── public_header_1.hpp
│ └── public_header_2.hpp
├── src
│ ├── private_header.hpp
│ └── source.cpp
├── tests
│ └── tests sources
└── CMakeLists.txt
```
But anyway, for a library there should be
1. public interface
2. private implementation
> However, it may be harder to start using something necessary that was hidden before in the internal folder.
The only reason it may be harder (regarding our project) is that the feature is not yet implemented for every platform. We have this problem right now. Some files/classes are used in the iOS project, but they are not implemented for Android. So, there is no single public interface for it.
This should be refactored/fixed but not in this PR.
There are also some files that are not used at all (and I doubt they will be used in the future) but I didn't want to remove them. At least in this PR.
All in all, I think the problem you described is not a real problem :)
1. It's not hard to create a public header for a new feature. Because it's just a header, right?
2. You won't use something "necessary" that was implemented before (and wasn't already used) and lacks public headers because:
1. If it lacks a public header then it's not fully implemented
2. You cannot use something that is not implemented, you need to implement it first
3. When you implement it, there is no problem with adding a public header for it.
P.S. I just reviewed these two file structures and must admit I like the second one more (with separate `include` and `src` folders).
@biodranik @vng WDYT?
rtsisyk
commented
Storing public headers in include/ and the implementation in src/ is a well-established practice and likely a good approach. Personally, I prefer a flatter, one-level structure, without the need for separate "native" and "http" folders. A more complex structure encourages people to explicitly increase complexity, whereas this entire library could likely be simplified and fit into just a few reasonably sized files. Storing public headers in include/ and the implementation in src/ is a well-established practice and likely a good approach. Personally, I prefer a flatter, one-level structure, without the need for separate "native" and "http" folders. A more complex structure encourages people to explicitly increase complexity, whereas this entire library could likely be simplified and fit into just a few reasonably sized files.
|
||||
internal/http/memory_request.hpp
|
||||
)
|
||||
|
||||
if (PLATFORM_MAC)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
internal/native/apple/http/background_uploader.mm
|
||||
internal/native/apple/http/client.mm
|
||||
internal/native/apple/http/session_manager.h
|
||||
internal/native/apple/http/session_manager.mm
|
||||
internal/native/apple/http/socket.mm
|
||||
internal/native/apple/http/thread.h
|
||||
internal/native/apple/http/thread.mm
|
||||
internal/native/apple/http/uploader.mm
|
||||
internal/native/apple/background_downloader.h
|
||||
internal/native/apple/background_downloader.mm
|
||||
internal/native/apple/network_policy.h
|
||||
internal/native/apple/network_policy.mm
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (PLATFORM_WIN OR PLATFORM_LINUX)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
internal/http/dummy_background_uploader.cpp
|
||||
internal/http/dummy_uploader.cpp
|
||||
internal/native/desktop/http/client.cpp
|
||||
internal/native/desktop/http/thread.cpp
|
||||
internal/native/desktop/http/thread.hpp
|
||||
internal/dummy_socket.cpp
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC ON)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC base
|
||||
PRIVATE coding
|
||||
PRIVATE platform
|
||||
PRIVATE $<$<BOOL:${PLATFORM_LINUX}>:Qt6::Network>
|
||||
PRIVATE $<$<BOOL:${PLATFORM_WIN}>:Qt6::Network>
|
||||
PRIVATE $<$<BOOL:${PLATFORM_MAC}>:
|
||||
-framework\ Foundation
|
||||
-framework\ CFNetwork
|
||||
-framework\ Security # SecPKCS12Import
|
||||
>
|
||||
)
|
||||
|
||||
omim_add_test_subdirectory(network_tests_support)
|
||||
omim_add_test_subdirectory(network_tests)
|
|
@ -1,40 +1,37 @@
|
|||
#include "platform/chunks_download_strategy.hpp"
|
||||
#include "chunks_download_strategy.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/varint.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace downloader
|
||||
namespace om::network
|
||||
{
|
||||
ChunksDownloadStrategy::ChunksDownloadStrategy(vector<string> const & urls)
|
||||
ChunksDownloadStrategy::ChunksDownloadStrategy(std::vector<std::string> const & urls)
|
||||
{
|
||||
// init servers list
|
||||
for (size_t i = 0; i < urls.size(); ++i)
|
||||
m_servers.push_back(ServerT(urls[i], SERVER_READY));
|
||||
for (const auto & url : urls)
|
||||
m_servers.emplace_back(url, SERVER_READY);
|
||||
}
|
||||
|
||||
pair<ChunksDownloadStrategy::ChunkT *, int>
|
||||
ChunksDownloadStrategy::GetChunk(RangeT const & range)
|
||||
std::pair<ChunksDownloadStrategy::ChunkT *, int> ChunksDownloadStrategy::GetChunk(RangeT const & range)
|
||||
{
|
||||
vector<ChunkT>::iterator i = lower_bound(m_chunks.begin(), m_chunks.end(), range.first, LessChunks());
|
||||
auto i = std::lower_bound(m_chunks.begin(), m_chunks.end(), range.first, LessChunks());
|
||||
|
||||
if (i != m_chunks.end() && i->m_pos == range.first)
|
||||
{
|
||||
ASSERT_EQUAL ( (i+1)->m_pos, range.second + 1, () );
|
||||
return pair<ChunkT *, int>(&(*i), distance(m_chunks.begin(), i));
|
||||
ASSERT_EQUAL((i + 1)->m_pos, range.second + 1, ());
|
||||
return {&(*i), distance(m_chunks.begin(), i)};
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LERROR, ("Downloader error. Invalid chunk range: ", range));
|
||||
return pair<ChunkT *, int>(static_cast<ChunkT *>(0), -1);
|
||||
return {nullptr, -1};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,28 +39,28 @@ void ChunksDownloadStrategy::InitChunks(int64_t fileSize, int64_t chunkSize, Chu
|
|||
{
|
||||
m_chunks.reserve(static_cast<size_t>(fileSize / chunkSize + 2));
|
||||
for (int64_t i = 0; i < fileSize; i += chunkSize)
|
||||
m_chunks.push_back(ChunkT(i, status));
|
||||
m_chunks.push_back(ChunkT(fileSize, CHUNK_AUX));
|
||||
m_chunks.emplace_back(i, status);
|
||||
m_chunks.emplace_back(fileSize, CHUNK_AUX);
|
||||
}
|
||||
|
||||
void ChunksDownloadStrategy::AddChunk(RangeT const & range, ChunkStatusT status)
|
||||
{
|
||||
ASSERT_LESS_OR_EQUAL ( range.first, range.second, () );
|
||||
ASSERT_LESS_OR_EQUAL(range.first, range.second, ());
|
||||
if (m_chunks.empty())
|
||||
{
|
||||
ASSERT_EQUAL ( range.first, 0, () );
|
||||
m_chunks.push_back(ChunkT(range.first, status));
|
||||
ASSERT_EQUAL(range.first, 0, ());
|
||||
m_chunks.emplace_back(range.first, status);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_EQUAL ( m_chunks.back().m_pos, range.first, () );
|
||||
ASSERT_EQUAL(m_chunks.back().m_pos, range.first, ());
|
||||
m_chunks.back().m_status = status;
|
||||
}
|
||||
|
||||
m_chunks.push_back(ChunkT(range.second + 1, CHUNK_AUX));
|
||||
m_chunks.emplace_back(range.second + 1, CHUNK_AUX);
|
||||
}
|
||||
|
||||
void ChunksDownloadStrategy::SaveChunks(int64_t fileSize, string const & fName)
|
||||
void ChunksDownloadStrategy::SaveChunks(int64_t fileSize, std::string const & fName)
|
||||
{
|
||||
if (!m_chunks.empty())
|
||||
{
|
||||
|
@ -81,15 +78,14 @@ void ChunksDownloadStrategy::SaveChunks(int64_t fileSize, string const & fName)
|
|||
}
|
||||
}
|
||||
|
||||
// Delete if no chunks or some error occured.
|
||||
// Delete if no chunks or some error occurred.
|
||||
UNUSED_VALUE(Platform::RemoveFileIfExists(fName));
|
||||
}
|
||||
|
||||
int64_t ChunksDownloadStrategy::LoadOrInitChunks(string const & fName, int64_t fileSize,
|
||||
int64_t chunkSize)
|
||||
int64_t ChunksDownloadStrategy::LoadOrInitChunks(std::string const & fName, int64_t fileSize, int64_t chunkSize)
|
||||
{
|
||||
ASSERT ( fileSize > 0, () );
|
||||
ASSERT ( chunkSize > 0, () );
|
||||
ASSERT(fileSize > 0, ());
|
||||
ASSERT(chunkSize > 0, ());
|
||||
|
||||
if (Platform::IsFileExistsByFullPath(fName))
|
||||
{
|
||||
|
@ -98,7 +94,7 @@ int64_t ChunksDownloadStrategy::LoadOrInitChunks(string const & fName, int64_t f
|
|||
FileReader r(fName);
|
||||
ReaderSource<FileReader> src(r);
|
||||
|
||||
int64_t const readSize = ReadVarInt<int64_t>(src);
|
||||
auto const readSize = ReadVarInt<int64_t>(src);
|
||||
if (readSize == fileSize)
|
||||
{
|
||||
// Load chunks.
|
||||
|
@ -133,10 +129,10 @@ int64_t ChunksDownloadStrategy::LoadOrInitChunks(string const & fName, int64_t f
|
|||
return 0;
|
||||
}
|
||||
|
||||
string ChunksDownloadStrategy::ChunkFinished(bool success, RangeT const & range)
|
||||
std::string ChunksDownloadStrategy::ChunkFinished(bool success, RangeT const & range)
|
||||
{
|
||||
pair<ChunkT *, int> res = GetChunk(range);
|
||||
string url;
|
||||
std::pair<ChunkT *, int> res = GetChunk(range);
|
||||
std::string url;
|
||||
// find server which was downloading this chunk
|
||||
if (res.first)
|
||||
{
|
||||
|
@ -153,8 +149,8 @@ string ChunksDownloadStrategy::ChunkFinished(bool success, RangeT const & range)
|
|||
}
|
||||
else
|
||||
{
|
||||
LOG(LINFO, ("Thread for url", m_servers[s].m_url,
|
||||
"failed to download chunk number", m_servers[s].m_chunkIndex));
|
||||
LOG(LINFO,
|
||||
("Thread for url", m_servers[s].m_url, "failed to download chunk number", m_servers[s].m_chunkIndex));
|
||||
// remove failed server and mark chunk as free
|
||||
m_servers.erase(m_servers.begin() + s);
|
||||
res.first->m_status = CHUNK_FREE;
|
||||
|
@ -166,30 +162,29 @@ string ChunksDownloadStrategy::ChunkFinished(bool success, RangeT const & range)
|
|||
return url;
|
||||
}
|
||||
|
||||
ChunksDownloadStrategy::ResultT
|
||||
ChunksDownloadStrategy::NextChunk(string & outUrl, RangeT & range)
|
||||
ChunksDownloadStrategy::ResultT ChunksDownloadStrategy::NextChunk(std::string & outUrl, RangeT & range)
|
||||
{
|
||||
// If no servers at all.
|
||||
if (m_servers.empty())
|
||||
return EDownloadFailed;
|
||||
|
||||
// Find first free server.
|
||||
ServerT * server = 0;
|
||||
for (size_t i = 0; i < m_servers.size(); ++i)
|
||||
ServerT * server = nullptr;
|
||||
for (auto & m_server : m_servers)
|
||||
{
|
||||
if (m_servers[i].m_chunkIndex == SERVER_READY)
|
||||
if (m_server.m_chunkIndex == SERVER_READY)
|
||||
{
|
||||
server = &m_servers[i];
|
||||
server = &m_server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (server == 0)
|
||||
if (!server)
|
||||
return ENoFreeServers;
|
||||
|
||||
bool allChunksDownloaded = true;
|
||||
|
||||
// Find first free chunk.
|
||||
for (size_t i = 0; i < m_chunks.size()-1; ++i)
|
||||
for (size_t i = 0; i < m_chunks.size() - 1; ++i)
|
||||
{
|
||||
switch (m_chunks[i].m_status)
|
||||
{
|
||||
|
@ -198,17 +193,15 @@ ChunksDownloadStrategy::NextChunk(string & outUrl, RangeT & range)
|
|||
outUrl = server->m_url;
|
||||
|
||||
range.first = m_chunks[i].m_pos;
|
||||
range.second = m_chunks[i+1].m_pos - 1;
|
||||
range.second = m_chunks[i + 1].m_pos - 1;
|
||||
|
||||
m_chunks[i].m_status = CHUNK_DOWNLOADING;
|
||||
return ENextChunk;
|
||||
|
||||
case CHUNK_DOWNLOADING:
|
||||
allChunksDownloaded = false;
|
||||
break;
|
||||
case CHUNK_DOWNLOADING: allChunksDownloaded = false; break;
|
||||
}
|
||||
}
|
||||
|
||||
return (allChunksDownloaded ? EDownloadSucceeded : ENoFreeServers);
|
||||
}
|
||||
} // namespace downloader
|
||||
} // namespace om::network
|
|
@ -5,13 +5,19 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace downloader
|
||||
namespace om::network
|
||||
{
|
||||
/// Single-threaded code
|
||||
class ChunksDownloadStrategy
|
||||
{
|
||||
public:
|
||||
enum ChunkStatusT { CHUNK_FREE = 0, CHUNK_DOWNLOADING = 1, CHUNK_COMPLETE = 2, CHUNK_AUX = -1 };
|
||||
enum ChunkStatusT
|
||||
{
|
||||
CHUNK_FREE = 0,
|
||||
CHUNK_DOWNLOADING = 1,
|
||||
CHUNK_COMPLETE = 2,
|
||||
CHUNK_AUX = -1
|
||||
};
|
||||
|
||||
private:
|
||||
#pragma pack(push, 1)
|
||||
|
@ -32,20 +38,20 @@ private:
|
|||
|
||||
struct LessChunks
|
||||
{
|
||||
bool operator() (ChunkT const & r1, ChunkT const & r2) const { return r1.m_pos < r2.m_pos; }
|
||||
bool operator() (ChunkT const & r1, int64_t const & r2) const { return r1.m_pos < r2; }
|
||||
bool operator() (int64_t const & r1, ChunkT const & r2) const { return r1 < r2.m_pos; }
|
||||
bool operator()(ChunkT const & r1, ChunkT const & r2) const { return r1.m_pos < r2.m_pos; }
|
||||
bool operator()(ChunkT const & r1, int64_t const & r2) const { return r1.m_pos < r2; }
|
||||
bool operator()(int64_t const & r1, ChunkT const & r2) const { return r1 < r2.m_pos; }
|
||||
};
|
||||
|
||||
using RangeT = std::pair<int64_t, int64_t>;
|
||||
|
||||
static const int SERVER_READY = -1;
|
||||
static constexpr int SERVER_READY = -1;
|
||||
struct ServerT
|
||||
{
|
||||
std::string m_url;
|
||||
int m_chunkIndex;
|
||||
|
||||
ServerT(std::string const & url, int ind) : m_url(url), m_chunkIndex(ind) {}
|
||||
ServerT(std::string url, int ind) : m_url(std::move(url)), m_chunkIndex(ind) {}
|
||||
};
|
||||
|
||||
std::vector<ChunkT> m_chunks;
|
||||
|
@ -56,7 +62,7 @@ private:
|
|||
std::pair<ChunkT *, int> GetChunk(RangeT const & range);
|
||||
|
||||
public:
|
||||
ChunksDownloadStrategy(std::vector<std::string> const & urls);
|
||||
explicit ChunksDownloadStrategy(std::vector<std::string> const & urls = {});
|
||||
|
||||
/// Init chunks vector for fileSize.
|
||||
void InitChunks(int64_t fileSize, int64_t chunkSize, ChunkStatusT status = CHUNK_FREE);
|
||||
|
@ -84,4 +90,4 @@ public:
|
|||
/// Should be called until returns ENextChunk
|
||||
ResultT NextChunk(std::string & outUrl, RangeT & range);
|
||||
};
|
||||
} // namespace downloader
|
||||
} // namespace om::network
|
30
network/download_status.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
enum class DownloadStatus
|
||||
{
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
FileNotFound,
|
||||
FailedSHA,
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(DownloadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case DownloadStatus::InProgress: return "In progress";
|
||||
case DownloadStatus::Completed: return "Completed";
|
||||
case DownloadStatus::Failed: return "Failed";
|
||||
case DownloadStatus::FileNotFound: return "File not found";
|
||||
case DownloadStatus::FailedSHA: return "Failed SHA check";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace om::network
|
|
@ -1,22 +1,19 @@
|
|||
#include "platform/downloader_utils.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
#include "platform/country_defines.hpp"
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
|
||||
#include "coding/url.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "std/target_os.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kMapsPath = "maps";
|
||||
std::string const kDiffsPath = "diffs";
|
||||
} // namespace
|
||||
|
||||
namespace downloader
|
||||
namespace om::network::downloader
|
||||
{
|
||||
|
||||
std::string GetFileDownloadUrl(std::string const & fileName, int64_t dataVersion, uint64_t diffVersion /* = 0 */)
|
||||
|
@ -55,10 +52,7 @@ bool IsUrlSupported(std::string const & url)
|
|||
}
|
||||
|
||||
size_t count = 0;
|
||||
strings::Tokenize(url::UrlDecode(urlComponents.back()), ".", [&count](std::string_view)
|
||||
{
|
||||
++count;
|
||||
});
|
||||
strings::Tokenize(url::UrlDecode(urlComponents.back()), ".", [&count](std::string_view) { ++count; });
|
||||
return count == 2;
|
||||
}
|
||||
|
||||
|
@ -79,4 +73,4 @@ std::string GetFilePathByUrl(std::string const & url)
|
|||
return platform::GetFileDownloadPath(dataVersion, mwmFile, fileType);
|
||||
}
|
||||
|
||||
} // namespace downloader
|
||||
} // namespace om::network::downloader
|
|
@ -3,10 +3,11 @@
|
|||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace downloader
|
||||
namespace om::network::downloader
|
||||
{
|
||||
std::string GetFileDownloadUrl(std::string const & fileName, int64_t dataVersion, uint64_t diffVersion = 0);
|
||||
bool IsUrlSupported(std::string const & url);
|
||||
std::string GetFilePathByUrl(std::string const & url);
|
||||
} // namespace downloader
|
||||
|
||||
bool IsUrlSupported(std::string const & url);
|
||||
|
||||
std::string GetFilePathByUrl(std::string const & url);
|
||||
} // namespace om::network::downloader
|
19
network/http/background_uploader.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "network/http/payload.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
class BackgroundUploader
|
||||
{
|
||||
public:
|
||||
explicit BackgroundUploader(Payload const & payload) : m_payload(payload) {}
|
||||
Payload const & GetPayload() const { return m_payload; }
|
||||
|
||||
// TODO add platform-specific implementation
|
||||
void Upload() const;
|
||||
|
||||
private:
|
||||
Payload m_payload;
|
||||
};
|
||||
} // namespace om::network::http
|
173
network/http/client.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
#include "client.hpp"
|
||||
|
||||
#include "coding/base64.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
![]() Is om:: namespace necessary/helpful? Is om:: namespace necessary/helpful?
AndrewShkrob
commented
I'd say so. I'd say so.
In the future (when all our libs have it) it should help to differentiate our libs from the others
If we decide to create some kind of SDK, it would also be good to have a namespace.
Just following best practices, you know :)
![]() Does it make sense to start following best practices in the whole code, and only when it is necessary? E.g. when we start working on the SDK? Otherwise, we'll have different mixed approaches in the code that will confuse developers. Does it make sense to start following best practices in the whole code, and only when it is necessary? E.g. when we start working on the SDK? Otherwise, we'll have different mixed approaches in the code that will confuse developers.
|
||||
{
|
||||
Client::Client(std::string const & url) : m_urlRequested(url) {}
|
||||
|
||||
bool Client::RunHttpRequest(std::string & response, SuccessChecker checker /* = nullptr */)
|
||||
{
|
||||
static auto const simpleChecker = [](Client const & request) { return request.ErrorCode() == 200; };
|
||||
|
||||
if (checker == nullptr)
|
||||
checker = simpleChecker;
|
||||
|
||||
if (RunHttpRequest() && checker(*this))
|
||||
{
|
||||
response = ServerResponse();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Client & Client::SetUrlRequested(std::string const & url)
|
||||
{
|
||||
m_urlRequested = url;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetHttpMethod(std::string const & method)
|
||||
{
|
||||
m_httpMethod = method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetBodyFile(std::string const & body_file, std::string const & content_type,
|
||||
std::string const & http_method /* = "POST" */,
|
||||
std::string const & content_encoding /* = "" */)
|
||||
{
|
||||
m_inputFile = body_file;
|
||||
m_bodyData.clear();
|
||||
m_headers.emplace("Content-Type", content_type);
|
||||
m_httpMethod = http_method;
|
||||
m_headers.emplace("Content-Encoding", content_encoding);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetReceivedFile(std::string const & received_file)
|
||||
{
|
||||
m_outputFile = received_file;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetUserAndPassword(std::string const & user, std::string const & password)
|
||||
{
|
||||
m_headers.emplace("Authorization", "Basic " + base64::Encode(user + ":" + password));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetCookies(std::string const & cookies)
|
||||
{
|
||||
m_cookies = cookies;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetFollowRedirects(bool followRedirects)
|
||||
{
|
||||
m_followRedirects = followRedirects;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetRawHeader(std::string const & key, std::string const & value)
|
||||
{
|
||||
m_headers.emplace(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client & Client::SetRawHeaders(Headers const & headers)
|
||||
{
|
||||
m_headers.insert(headers.begin(), headers.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Client::SetTimeout(double timeoutSec) { m_timeoutSec = timeoutSec; }
|
||||
|
||||
std::string const & Client::UrlRequested() const { return m_urlRequested; }
|
||||
|
||||
std::string const & Client::UrlReceived() const { return m_urlReceived; }
|
||||
|
||||
bool Client::WasRedirected() const { return m_urlRequested != m_urlReceived; }
|
||||
|
||||
int Client::ErrorCode() const { return m_errorCode; }
|
||||
|
||||
std::string const & Client::ServerResponse() const { return m_serverResponse; }
|
||||
|
||||
std::string const & Client::HttpMethod() const { return m_httpMethod; }
|
||||
|
||||
std::string Client::CombinedCookies() const
|
||||
{
|
||||
std::string serverCookies;
|
||||
auto const it = m_headers.find("Set-Cookie");
|
||||
if (it != m_headers.end())
|
||||
serverCookies = it->second;
|
||||
|
||||
if (serverCookies.empty())
|
||||
return m_cookies;
|
||||
|
||||
if (m_cookies.empty())
|
||||
return serverCookies;
|
||||
|
||||
return serverCookies + "; " + m_cookies;
|
||||
}
|
||||
|
||||
std::string Client::CookieByName(std::string name) const
|
||||
{
|
||||
std::string const str = CombinedCookies();
|
||||
name += "=";
|
||||
auto const cookie = str.find(name);
|
||||
auto const eq = cookie + name.size();
|
||||
if (cookie != std::string::npos && str.size() > eq)
|
||||
return str.substr(eq, str.find(';', eq) - eq);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Client::LoadHeaders(bool loadHeaders) { m_loadHeaders = loadHeaders; }
|
||||
|
||||
Client::Headers const & Client::GetHeaders() const { return m_headers; }
|
||||
|
||||
// static
|
||||
std::string Client::NormalizeServerCookies(std::string && cookies)
|
||||
{
|
||||
std::istringstream is(cookies);
|
||||
std::string str, result;
|
||||
|
||||
// Split by ", ". Can have invalid tokens here, expires= can also contain a comma.
|
||||
while (getline(is, str, ','))
|
||||
{
|
||||
size_t const leading = str.find_first_not_of(' ');
|
||||
if (leading != std::string::npos)
|
||||
str.substr(leading).swap(str);
|
||||
|
||||
// In the good case, we have '=' and it goes before any ' '.
|
||||
auto const eq = str.find('=');
|
||||
if (eq == std::string::npos)
|
||||
continue; // It's not a cookie: no valid key value pair.
|
||||
|
||||
auto const sp = str.find(' ');
|
||||
if (sp != std::string::npos && eq > sp)
|
||||
continue; // It's not a cookie: comma in expires date.
|
||||
|
||||
// Insert delimiter.
|
||||
if (!result.empty())
|
||||
result.append("; ");
|
||||
|
||||
// Read cookie itself.
|
||||
result.append(str, 0, str.find(';'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string DebugPrint(Client const & request)
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << "HTTP " << request.ErrorCode() << " url [" << request.UrlRequested() << "]";
|
||||
if (request.WasRedirected())
|
||||
ostr << " was redirected to [" << request.UrlReceived() << "]";
|
||||
if (!request.ServerResponse().empty())
|
||||
ostr << " response: " << request.ServerResponse();
|
||||
return ostr.str();
|
||||
}
|
||||
} // namespace om::network::http
|
|
@ -1,26 +1,3 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Alexander Borsuk <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*******************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
@ -30,9 +7,9 @@ SOFTWARE.
|
|||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
class HttpClient
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
static auto constexpr kNoError = -1;
|
||||
|
@ -44,34 +21,32 @@ public:
|
|||
};
|
||||
|
||||
using Headers = std::unordered_map<std::string, std::string>;
|
||||
using SuccessChecker = std::function<bool(Client const & request)>;
|
||||
|
||||
HttpClient() = default;
|
||||
explicit HttpClient(std::string const & url);
|
||||
explicit Client(std::string const & url);
|
||||
|
||||
// Synchronous (blocking) call, should be implemented for each platform
|
||||
// @returns true if connection was made and server returned something (200, 404, etc.).
|
||||
// @note Implementations should transparently support all needed HTTP redirects.
|
||||
// Implemented for each platform.
|
||||
bool RunHttpRequest();
|
||||
using SuccessChecker = std::function<bool(HttpClient const & request)>;
|
||||
|
||||
// Returns true and copy of server response into [response] in case when RunHttpRequest() and
|
||||
// [checker] return true. When [checker] is equal to nullptr then default checker will be used.
|
||||
// Check by default: ErrorCode() == 200
|
||||
bool RunHttpRequest(std::string & response, SuccessChecker checker = nullptr);
|
||||
|
||||
HttpClient & SetUrlRequested(std::string const & url);
|
||||
HttpClient & SetHttpMethod(std::string const & method);
|
||||
Client & SetUrlRequested(std::string const & url);
|
||||
Client & SetHttpMethod(std::string const & method);
|
||||
// This method is mutually exclusive with set_body_data().
|
||||
HttpClient & SetBodyFile(std::string const & body_file, std::string const & content_type,
|
||||
std::string const & http_method = "POST",
|
||||
std::string const & content_encoding = "");
|
||||
Client & SetBodyFile(std::string const & body_file, std::string const & content_type,
|
||||
std::string const & http_method = "POST", std::string const & content_encoding = "");
|
||||
// If set, stores server reply in file specified.
|
||||
HttpClient & SetReceivedFile(std::string const & received_file);
|
||||
Client & SetReceivedFile(std::string const & received_file);
|
||||
// This method is mutually exclusive with set_body_file().
|
||||
template <typename StringT>
|
||||
HttpClient & SetBodyData(StringT && body_data, std::string const & content_type,
|
||||
std::string const & http_method = "POST",
|
||||
std::string const & content_encoding = {})
|
||||
Client & SetBodyData(StringT && body_data, std::string const & content_type, std::string const & http_method = "POST",
|
||||
std::string const & content_encoding = {})
|
||||
{
|
||||
m_bodyData = std::forward<StringT>(body_data);
|
||||
m_inputFile.clear();
|
||||
|
@ -82,13 +57,13 @@ public:
|
|||
return *this;
|
||||
}
|
||||
// HTTP Basic Auth.
|
||||
HttpClient & SetUserAndPassword(std::string const & user, std::string const & password);
|
||||
Client & SetUserAndPassword(std::string const & user, std::string const & password);
|
||||
// Set HTTP Cookie header.
|
||||
HttpClient & SetCookies(std::string const & cookies);
|
||||
Client & SetCookies(std::string const & cookies);
|
||||
// When set to false (default), clients never get 3XX codes from servers, redirects are handled automatically.
|
||||
HttpClient & SetFollowRedirects(bool follow_redirects);
|
||||
HttpClient & SetRawHeader(std::string const & key, std::string const & value);
|
||||
HttpClient & SetRawHeaders(Headers const & headers);
|
||||
Client & SetFollowRedirects(bool follow_redirects);
|
||||
Client & SetRawHeader(std::string const & key, std::string const & value);
|
||||
Client & SetRawHeaders(Headers const & headers);
|
||||
void SetTimeout(double timeoutSec);
|
||||
|
||||
std::string const & UrlRequested() const;
|
||||
|
@ -128,13 +103,14 @@ private:
|
|||
// Cookies set by the client before request is run.
|
||||
std::string m_cookies;
|
||||
Headers m_headers;
|
||||
bool m_followRedirects = false; // If true then in case of HTTP response 3XX make another request to follow redirected URL
|
||||
bool m_followRedirects =
|
||||
false; // If true then in case of HTTP response 3XX make another request to follow redirected URL
|
||||
bool m_loadHeaders = false;
|
||||
// Use 30 seconds timeout by default.
|
||||
double m_timeoutSec = 30.0;
|
||||
|
||||
DISALLOW_COPY_AND_MOVE(HttpClient);
|
||||
DISALLOW_COPY_AND_MOVE(Client);
|
||||
};
|
||||
|
||||
std::string DebugPrint(HttpClient const & request);
|
||||
} // namespace platform
|
||||
std::string DebugPrint(Client const & request);
|
||||
} // namespace om::network::http
|
|
@ -3,18 +3,16 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
struct HttpPayload
|
||||
struct Payload
|
||||
{
|
||||
HttpPayload();
|
||||
|
||||
std::string m_method;
|
||||
std::string m_method = "POST";
|
||||
std::string m_url;
|
||||
std::map<std::string, std::string> m_params;
|
||||
std::map<std::string, std::string> m_headers;
|
||||
std::string m_fileKey;
|
||||
std::string m_fileKey = "file";
|
||||
std::string m_filePath;
|
||||
bool m_needClientAuth;
|
||||
bool m_needClientAuth = false;
|
||||
};
|
||||
} // namespace platform
|
||||
} // namespace om::network::http
|
44
network/http/request.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#include "request.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "network/internal/http/file_request.hpp"
|
||||
#include "network/internal/http/memory_request.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
Request::Request(Callback && onFinish, Callback && onProgress)
|
||||
: m_status(DownloadStatus::InProgress)
|
||||
, m_progress(Progress::Unknown())
|
||||
, m_onFinish(std::move(onFinish))
|
||||
, m_onProgress(std::move(onProgress))
|
||||
{
|
||||
}
|
||||
|
||||
Request * Request::Get(std::string const & url, Callback && onFinish, Callback && onProgress)
|
||||
{
|
||||
return new internal::MemoryRequest(url, std::move(onFinish), std::move(onProgress));
|
||||
}
|
||||
|
||||
Request * Request::PostJson(std::string const & url, std::string const & postData, Callback && onFinish,
|
||||
Callback && onProgress)
|
||||
{
|
||||
return new internal::MemoryRequest(url, postData, std::move(onFinish), std::move(onProgress));
|
||||
}
|
||||
|
||||
Request * Request::GetFile(std::vector<std::string> const & urls, std::string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish, Callback && onProgress, int64_t chunkSize, bool doCleanOnCancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new internal::FileRequest(urls, filePath, fileSize, std::move(onFinish), std::move(onProgress), chunkSize,
|
||||
doCleanOnCancel);
|
||||
}
|
||||
catch (FileWriter::Exception const & e)
|
||||
{
|
||||
// Can't create or open file for writing.
|
||||
LOG(LWARNING, ("Can't create file", filePath, "with size", fileSize, e.Msg()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace om::network::http
|
42
network/http/request.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "network/download_status.hpp"
|
||||
#include "network/progress.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
using Callback = std::function<void(Request & request)>;
|
||||
|
||||
public:
|
||||
virtual ~Request() = default;
|
||||
|
||||
DownloadStatus GetStatus() const { return m_status; }
|
||||
Progress const & GetProgress() const { return m_progress; }
|
||||
/// Either file path (for chunks) or downloaded data
|
||||
virtual std::string const & GetData() const = 0;
|
||||
|
||||
/// Response saved to memory buffer and retrieved with Data()
|
||||
static Request * Get(std::string const & url, Callback && onFinish, Callback && onProgress = Callback());
|
||||
|
||||
/// Content-type for request is always "application/json"
|
||||
static Request * PostJson(std::string const & url, std::string const & postData, Callback && onFinish,
|
||||
Callback && onProgress = Callback());
|
||||
|
||||
/// Download file to filePath.
|
||||
/// @param[in] fileSize Correct file size (needed for resuming and reserving).
|
||||
static Request * GetFile(std::vector<std::string> const & urls, std::string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish, Callback && onProgress = Callback(), int64_t chunkSize = 512 * 1024,
|
||||
bool doCleanOnCancel = true);
|
||||
|
||||
protected:
|
||||
DownloadStatus m_status;
|
||||
Progress m_progress;
|
||||
Callback m_onFinish;
|
||||
Callback m_onProgress;
|
||||
|
||||
Request(Callback && onFinish, Callback && onProgress);
|
||||
};
|
||||
} // namespace om::network::http
|
19
network/http/thread.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "network/http/thread_callback.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
class Thread;
|
||||
|
||||
namespace thread
|
||||
{
|
||||
Thread * CreateThread(std::string const & url, IThreadCallback & cb, int64_t begRange = 0, int64_t endRange = -1,
|
||||
int64_t expectedSize = -1, std::string const & postBody = {});
|
||||
|
||||
void DeleteThread(Thread * thread);
|
||||
} // namespace thread
|
||||
} // namespace om::network::http
|
|
@ -1,16 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace downloader
|
||||
namespace om::network::http
|
||||
{
|
||||
class IHttpThreadCallback
|
||||
class IThreadCallback
|
||||
{
|
||||
public:
|
||||
virtual bool OnWrite(int64_t offset, void const * buffer, size_t size) = 0;
|
||||
virtual void OnFinish(long httpOrErrorCode, int64_t begRange, int64_t endRange) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~IHttpThreadCallback() = default;
|
||||
virtual ~IThreadCallback() = default;
|
||||
};
|
||||
} // namespace downloader
|
||||
} // namespace om::network::http
|
28
network/http/uploader.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "network/http/payload.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
class Uploader
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
int32_t m_httpCode = 0;
|
||||
std::string m_description;
|
||||
};
|
||||
|
||||
explicit Uploader(Payload const & payload) : m_payload(payload) {}
|
||||
Payload const & GetPayload() const { return m_payload; }
|
||||
Result Upload() const;
|
||||
|
||||
private:
|
||||
Payload const m_payload;
|
||||
};
|
||||
} // namespace om::network::http
|
6
network/internal/dummy_network_policy.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "network/network_policy.hpp"
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
NetworkPolicy GetCurrentNetworkPolicy() { return NetworkPolicy{true}; }
|
||||
} // namespace om::network
|
16
network/internal/dummy_socket.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "network/socket.hpp"
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
class DummySocket final : public Socket
|
||||
{
|
||||
public:
|
||||
bool Open(std::string const &, uint16_t) override { return false; }
|
||||
void Close() override {}
|
||||
bool Read(uint8_t *, uint32_t) override { return false; }
|
||||
bool Write(uint8_t const *, uint32_t) override { return false; }
|
||||
void SetTimeout(uint32_t) override {}
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> CreateSocket() { return std::make_unique<DummySocket>(); }
|
||||
} // namespace om::network
|
6
network/internal/http/dummy_background_uploader.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "network/http/background_uploader.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
void BackgroundUploader::Upload() const {}
|
||||
} // namespace om::network::http
|
10
network/internal/http/dummy_uploader.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include "network/http/uploader.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
Uploader::Result Uploader::Upload() const
|
||||
{
|
||||
// Dummy implementation.
|
||||
return {};
|
||||
}
|
||||
} // namespace om::network::http
|
239
network/internal/http/file_request.hpp
Normal file
|
@ -0,0 +1,239 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "network/chunks_download_strategy.hpp"
|
||||
#include "network/http/request.hpp"
|
||||
#include "network/http/thread.hpp"
|
||||
#include "network/non_http_error_code.hpp"
|
||||
|
||||
namespace om::network::http::internal
|
||||
{
|
||||
class FileRequest : public Request, public IThreadCallback
|
||||
{
|
||||
using ThreadHandleT = std::pair<Thread *, int64_t>;
|
||||
using ThreadsContainerT = std::list<ThreadHandleT>;
|
||||
|
||||
class ThreadByPos
|
||||
{
|
||||
int64_t m_pos;
|
||||
|
||||
public:
|
||||
explicit ThreadByPos(int64_t pos) : m_pos(pos) {}
|
||||
inline bool operator()(ThreadHandleT const & p) const { return (p.second == m_pos); }
|
||||
};
|
||||
|
||||
public:
|
||||
FileRequest(std::vector<std::string> const & urls, std::string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish, Callback && onProgress, int64_t chunkSize, bool doCleanProgressFiles)
|
||||
: Request(std::move(onFinish), std::move(onProgress))
|
||||
, m_strategy(urls)
|
||||
, m_filePath(filePath)
|
||||
, m_goodChunksCount(0)
|
||||
, m_doCleanProgressFiles(doCleanProgressFiles)
|
||||
{
|
||||
ASSERT(!urls.empty(), ());
|
||||
|
||||
// Load resume downloading information.
|
||||
m_progress.m_bytesDownloaded = m_strategy.LoadOrInitChunks(m_filePath + RESUME_FILE_EXTENSION, fileSize, chunkSize);
|
||||
m_progress.m_bytesTotal = fileSize;
|
||||
|
||||
FileWriter::Op openMode = FileWriter::OP_WRITE_TRUNCATE;
|
||||
if (m_progress.m_bytesDownloaded != 0)
|
||||
{
|
||||
// Check that resume information is correct with existing file.
|
||||
uint64_t size;
|
||||
if (base::GetFileSize(filePath + DOWNLOADING_FILE_EXTENSION, size) && size <= static_cast<uint64_t>(fileSize))
|
||||
openMode = FileWriter::OP_WRITE_EXISTING;
|
||||
else
|
||||
m_strategy.InitChunks(fileSize, chunkSize);
|
||||
}
|
||||
|
||||
// Create file and reserve needed size.
|
||||
std::unique_ptr<FileWriter> writer(new FileWriter(filePath + DOWNLOADING_FILE_EXTENSION, openMode));
|
||||
|
||||
// Assign here, because previous functions can throw an exception.
|
||||
m_writer.swap(writer);
|
||||
Platform::DisableBackupForFile(filePath + DOWNLOADING_FILE_EXTENSION);
|
||||
StartThreads();
|
||||
}
|
||||
|
||||
~FileRequest() override
|
||||
{
|
||||
// Do safe delete with removing from list in case if DeleteNativeHttpThread
|
||||
// can produce final notifications to this->OnFinish().
|
||||
while (!m_threads.empty())
|
||||
{
|
||||
Thread * p = m_threads.back().first;
|
||||
m_threads.pop_back();
|
||||
thread::DeleteThread(p);
|
||||
}
|
||||
|
||||
if (m_status == DownloadStatus::InProgress)
|
||||
{
|
||||
// means that client canceled download process, so delete all temporary files
|
||||
CloseWriter();
|
||||
|
||||
if (m_doCleanProgressFiles)
|
||||
{
|
||||
Platform::RemoveFileIfExists(m_filePath + DOWNLOADING_FILE_EXTENSION);
|
||||
Platform::RemoveFileIfExists(m_filePath + RESUME_FILE_EXTENSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string const & GetData() const override { return m_filePath; }
|
||||
|
||||
private:
|
||||
ChunksDownloadStrategy::ResultT StartThreads()
|
||||
{
|
||||
std::string url;
|
||||
std::pair<int64_t, int64_t> range;
|
||||
ChunksDownloadStrategy::ResultT result;
|
||||
while ((result = m_strategy.NextChunk(url, range)) == ChunksDownloadStrategy::ENextChunk)
|
||||
{
|
||||
Thread * p = thread::CreateThread(url, *this, range.first, range.second, m_progress.m_bytesTotal);
|
||||
ASSERT(p, ());
|
||||
m_threads.emplace_back(p, range.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void RemoveHttpThreadByKey(int64_t begRange)
|
||||
{
|
||||
auto it = std::find_if(m_threads.begin(), m_threads.end(), ThreadByPos(begRange));
|
||||
if (it != m_threads.end())
|
||||
{
|
||||
Thread * p = it->first;
|
||||
m_threads.erase(it);
|
||||
thread::DeleteThread(p);
|
||||
}
|
||||
else
|
||||
LOG(LERROR, ("Tried to remove invalid thread for position", begRange));
|
||||
}
|
||||
|
||||
bool OnWrite(int64_t offset, void const * buffer, size_t size) override
|
||||
{
|
||||
#ifdef DEBUG
|
||||
static threads::ThreadID const id = threads::GetCurrentThreadID();
|
||||
ASSERT_EQUAL(id, threads::GetCurrentThreadID(), ("OnWrite called from different threads"));
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
m_writer->Seek(offset);
|
||||
m_writer->Write(buffer, size);
|
||||
return true;
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't write buffer for size", size, e.Msg()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveResumeChunks()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Flush writer before saving downloaded chunks.
|
||||
m_writer->Flush();
|
||||
|
||||
m_strategy.SaveChunks(m_progress.m_bytesTotal, m_filePath + RESUME_FILE_EXTENSION);
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't flush writer", e.Msg()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Called for each chunk by one main (GUI) thread.
|
||||
void OnFinish(long httpOrErrorCode, int64_t begRange, int64_t endRange) override
|
||||
{
|
||||
#ifdef DEBUG
|
||||
static threads::ThreadID const id = threads::GetCurrentThreadID();
|
||||
ASSERT_EQUAL(id, threads::GetCurrentThreadID(), ("OnFinish called from different threads"));
|
||||
#endif
|
||||
|
||||
bool const isChunkOk = (httpOrErrorCode == 200);
|
||||
UNUSED_VALUE(m_strategy.ChunkFinished(isChunkOk, {begRange, endRange}));
|
||||
|
||||
// remove completed chunk from the list, beg is the key
|
||||
RemoveHttpThreadByKey(begRange);
|
||||
|
||||
// report progress
|
||||
if (isChunkOk)
|
||||
{
|
||||
m_progress.m_bytesDownloaded += (endRange - begRange) + 1;
|
||||
if (m_onProgress)
|
||||
m_onProgress(*this);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const message = non_http_error_code::DebugPrint(httpOrErrorCode);
|
||||
LOG(LWARNING, (m_filePath, "HttpRequest error:", message));
|
||||
}
|
||||
|
||||
ChunksDownloadStrategy::ResultT const result = StartThreads();
|
||||
if (result == ChunksDownloadStrategy::EDownloadFailed)
|
||||
m_status = httpOrErrorCode == 404 ? DownloadStatus::FileNotFound : DownloadStatus::Failed;
|
||||
else if (result == ChunksDownloadStrategy::EDownloadSucceeded)
|
||||
m_status = DownloadStatus::Completed;
|
||||
|
||||
if (isChunkOk)
|
||||
{
|
||||
// save information for download resume
|
||||
++m_goodChunksCount;
|
||||
if (m_status != DownloadStatus::Completed && m_goodChunksCount % 10 == 0)
|
||||
SaveResumeChunks();
|
||||
}
|
||||
|
||||
if (m_status == DownloadStatus::InProgress)
|
||||
return;
|
||||
|
||||
// 1. Save downloaded chunks if some error occured.
|
||||
if (m_status == DownloadStatus::Failed || m_status == DownloadStatus::FileNotFound)
|
||||
SaveResumeChunks();
|
||||
|
||||
// 2. Free file handle.
|
||||
CloseWriter();
|
||||
|
||||
// 3. Clean up resume file with chunks range on success
|
||||
if (m_status == DownloadStatus::Completed)
|
||||
{
|
||||
Platform::RemoveFileIfExists(m_filePath + RESUME_FILE_EXTENSION);
|
||||
|
||||
// Rename finished file to it's original name.
|
||||
Platform::RemoveFileIfExists(m_filePath);
|
||||
base::RenameFileX(m_filePath + DOWNLOADING_FILE_EXTENSION, m_filePath);
|
||||
}
|
||||
|
||||
// 4. Finish downloading.
|
||||
m_onFinish(*this);
|
||||
}
|
||||
|
||||
void CloseWriter()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_writer.reset();
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't close file correctly", e.Msg()));
|
||||
|
||||
m_status = DownloadStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
ChunksDownloadStrategy m_strategy;
|
||||
ThreadsContainerT m_threads;
|
||||
std::string m_filePath;
|
||||
std::unique_ptr<FileWriter> m_writer;
|
||||
|
||||
size_t m_goodChunksCount;
|
||||
bool m_doCleanProgressFiles;
|
||||
};
|
||||
} // namespace om::network::http::internal
|
69
network/internal/http/memory_request.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "network/download_status.hpp"
|
||||
#include "network/http/request.hpp"
|
||||
#include "network/http/thread.hpp"
|
||||
#include "network/non_http_error_code.hpp"
|
||||
|
||||
namespace om::network::http::internal
|
||||
{
|
||||
/// Stores server response into the memory
|
||||
class MemoryRequest : public Request, public IThreadCallback
|
||||
{
|
||||
public:
|
||||
MemoryRequest(std::string const & url, Callback && onFinish, Callback && onProgress)
|
||||
: Request(std::move(onFinish), std::move(onProgress)), m_requestUrl(url), m_writer(m_downloadedData)
|
||||
{
|
||||
m_thread = thread::CreateThread(url, *this);
|
||||
ASSERT(m_thread, ());
|
||||
}
|
||||
|
||||
MemoryRequest(std::string const & url, std::string const & postData, Callback && onFinish, Callback && onProgress)
|
||||
: Request(std::move(onFinish), std::move(onProgress)), m_writer(m_downloadedData)
|
||||
{
|
||||
m_thread = thread::CreateThread(url, *this, 0, -1, -1, postData);
|
||||
ASSERT(m_thread, ());
|
||||
}
|
||||
|
||||
~MemoryRequest() override { thread::DeleteThread(m_thread); }
|
||||
|
||||
std::string const & GetData() const override { return m_downloadedData; }
|
||||
|
||||
private:
|
||||
bool OnWrite(int64_t, void const * buffer, size_t size) override
|
||||
{
|
||||
m_writer.Write(buffer, size);
|
||||
m_progress.m_bytesDownloaded += size;
|
||||
if (m_onProgress)
|
||||
m_onProgress(*this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnFinish(long httpOrErrorCode, int64_t, int64_t) override
|
||||
{
|
||||
if (httpOrErrorCode == 200)
|
||||
{
|
||||
m_status = DownloadStatus::Completed;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const message = non_http_error_code::DebugPrint(httpOrErrorCode);
|
||||
LOG(LWARNING, ("HttpRequest error:", message));
|
||||
if (httpOrErrorCode == 404)
|
||||
m_status = DownloadStatus::FileNotFound;
|
||||
else
|
||||
m_status = DownloadStatus::Failed;
|
||||
}
|
||||
|
||||
m_onFinish(*this);
|
||||
}
|
||||
|
||||
Thread * m_thread;
|
||||
|
||||
std::string m_requestUrl;
|
||||
std::string m_downloadedData;
|
||||
MemWriter<std::string> m_writer;
|
||||
};
|
||||
} // namespace om::network::http::internal
|
|
@ -1,7 +1,8 @@
|
|||
#import "platform/background_downloader_ios.h"
|
||||
#import "background_downloader.h"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "platform/downloader_utils.hpp"
|
||||
|
||||
#include "network/downloader/utils.hpp"
|
||||
|
||||
// How many seconds to wait before the request fails.
|
||||
static constexpr NSTimeInterval kTimeoutIntervalInSeconds = 10;
|
||||
|
@ -44,7 +45,7 @@ static constexpr NSTimeInterval kTimeoutIntervalInSeconds = 10;
|
|||
@implementation MapFileSaveStrategy
|
||||
|
||||
- (NSURL *)getLocationForWebUrl:(NSURL *)webUrl {
|
||||
NSString *path = @(downloader::GetFilePathByUrl(webUrl.path.UTF8String).c_str());
|
||||
NSString *path = @(om::network::downloader::GetFilePathByUrl(webUrl.path.UTF8String).c_str());
|
||||
return [NSURL fileURLWithPath:path];
|
||||
}
|
||||
|
101
network/internal/native/apple/http/background_uploader.mm
Normal file
|
@ -0,0 +1,101 @@
|
|||
#include "network/http/background_uploader.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
static NSString * const kSessionId = @"MWMBackgroundUploader_sessionId";
|
||||
|
||||
@interface MWMBackgroundUploader : NSObject <NSURLSessionDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSURLSession * session;
|
||||
|
||||
+ (MWMBackgroundUploader *)sharedUploader;
|
||||
- (void)upload:(om::network::http::Payload const &)payload;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MWMBackgroundUploader
|
||||
|
||||
+ (MWMBackgroundUploader *)sharedUploader
|
||||
{
|
||||
static MWMBackgroundUploader * uploader;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
uploader = [[MWMBackgroundUploader alloc] init];
|
||||
});
|
||||
return uploader;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
NSURLSessionConfiguration * config =
|
||||
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSessionId];
|
||||
config.allowsCellularAccess = NO;
|
||||
config.sessionSendsLaunchEvents = NO;
|
||||
config.discretionary = YES;
|
||||
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)upload:(om::network::http::Payload const &)payload
|
||||
{
|
||||
NSURLComponents * components = [[NSURLComponents alloc] initWithString:@(payload.m_url.c_str())];
|
||||
NSMutableArray * newQueryItems = [NSMutableArray arrayWithArray:components.queryItems];
|
||||
std::for_each(payload.m_params.begin(), payload.m_params.end(),
|
||||
[newQueryItems](auto const & pair)
|
||||
{
|
||||
[newQueryItems addObject:[NSURLQueryItem queryItemWithName:@(pair.first.c_str())
|
||||
value:@(pair.second.c_str())]];
|
||||
});
|
||||
[components setQueryItems:newQueryItems];
|
||||
NSURL * url = components.URL;
|
||||
CHECK(url, ());
|
||||
|
||||
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
|
||||
request.HTTPMethod = @(payload.m_method.c_str());
|
||||
std::for_each(payload.m_headers.begin(), payload.m_headers.end(), [request](auto const & pair)
|
||||
{ [request addValue:@(pair.second.c_str()) forHTTPHeaderField:@(pair.first.c_str())]; });
|
||||
|
||||
NSURL * fileUrl = [NSURL fileURLWithPath:@(payload.m_filePath.c_str())];
|
||||
CHECK(fileUrl, ());
|
||||
|
||||
[[self.session uploadTaskWithRequest:request fromFile:fileUrl] resume];
|
||||
NSError * error;
|
||||
[[NSFileManager defaultManager] removeItemAtURL:fileUrl error:&error];
|
||||
if (error)
|
||||
{
|
||||
LOG(LDEBUG, ([error description].UTF8String));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
LOG(LDEBUG, ("Upload failed:", [error description].UTF8String));
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential * _Nullable credential))completionHandler
|
||||
{
|
||||
NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
void BackgroundUploader::Upload() const { [[MWMBackgroundUploader sharedUploader] upload:GetPayload()]; }
|
||||
} // namespace om::network::http
|
|
@ -1,52 +1,28 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Alexander Borsuk <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag
|
||||
#endif
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
#import "platform/http_session_manager.h"
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface RedirectDelegate : NSObject<NSURLSessionDataDelegate>
|
||||
#import "network/internal/native/apple/http/session_manager.h"
|
||||
|
||||
// If YES - redirect response triggeres new request automatically
|
||||
@interface RedirectDelegate : NSObject <NSURLSessionDataDelegate>
|
||||
|
||||
// If YES - redirect response triggers new request automatically
|
||||
// If NO - redirect response is returned to result handler
|
||||
@property(nonatomic) BOOL followRedirects;
|
||||
|
||||
- (instancetype)init:(BOOL)followRedirects;
|
||||
|
||||
- (void) URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)newRequest
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler;
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)newRequest
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler;
|
||||
@end
|
||||
|
||||
@implementation RedirectDelegate
|
||||
|
@ -54,15 +30,15 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
{
|
||||
if (self = [super init])
|
||||
_followRedirects = followRedirects;
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)newRequest
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)newRequest
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
{
|
||||
if (!_followRedirects && response.statusCode >= 300 && response.statusCode < 400)
|
||||
completionHandler(nil);
|
||||
|
@ -71,7 +47,6 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface Connection : NSObject
|
||||
+ (nullable NSData *)sendSynchronousRequest:(NSURLRequest *)request
|
||||
followRedirects:(BOOL)followRedirects
|
||||
|
@ -87,13 +62,17 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
Connection * connection = [[Connection alloc] init];
|
||||
return [connection sendSynchronousRequest:request followRedirects: followRedirects returningResponse:response error:error];
|
||||
return [connection sendSynchronousRequest:request
|
||||
followRedirects:followRedirects
|
||||
returningResponse:response
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (NSData *)sendSynchronousRequest:(NSURLRequest *)request
|
||||
followRedirects:(BOOL)followRedirects
|
||||
returningResponse:(NSURLResponse * __autoreleasing *)response
|
||||
error:(NSError * __autoreleasing *)error {
|
||||
error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
__block NSData * resultData = nil;
|
||||
__block NSURLResponse * resultResponse = nil;
|
||||
__block NSError * resultError = nil;
|
||||
|
@ -101,13 +80,12 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
|
||||
RedirectDelegate * delegate = [[RedirectDelegate alloc] init: followRedirects];
|
||||
RedirectDelegate * delegate = [[RedirectDelegate alloc] init:followRedirects];
|
||||
|
||||
[[[HttpSessionManager sharedManager]
|
||||
dataTaskWithRequest:request
|
||||
delegate:delegate
|
||||
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response,
|
||||
NSError * _Nullable error) {
|
||||
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
resultData = data;
|
||||
resultResponse = response;
|
||||
resultError = error;
|
||||
|
@ -121,13 +99,14 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
}
|
||||
@end
|
||||
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
bool HttpClient::RunHttpRequest()
|
||||
bool Client::RunHttpRequest()
|
||||
{
|
||||
NSURL * url = static_cast<NSURL *>([NSURL URLWithString:@(m_urlRequested.c_str())]);
|
||||
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL: url
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:m_timeoutSec];
|
||||
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:m_timeoutSec];
|
||||
// We handle cookies manually.
|
||||
request.HTTPShouldHandleCookies = NO;
|
||||
|
||||
|
@ -150,7 +129,8 @@ bool HttpClient::RunHttpRequest()
|
|||
{
|
||||
NSError * err = nil;
|
||||
NSString * path = [NSString stringWithUTF8String:m_inputFile.c_str()];
|
||||
const unsigned long long file_size = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize;
|
||||
const unsigned long long file_size =
|
||||
[[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize;
|
||||
if (err)
|
||||
{
|
||||
m_errorCode = static_cast<int>(err.code);
|
||||
|
@ -165,7 +145,10 @@ bool HttpClient::RunHttpRequest()
|
|||
|
||||
NSHTTPURLResponse * response = nil;
|
||||
NSError * err = nil;
|
||||
NSData * url_data = [Connection sendSynchronousRequest:request followRedirects:m_followRedirects returningResponse:&response error:&err];
|
||||
NSData * url_data = [Connection sendSynchronousRequest:request
|
||||
followRedirects:m_followRedirects
|
||||
returningResponse:&response
|
||||
error:&err];
|
||||
|
||||
m_headers.clear();
|
||||
|
||||
|
@ -181,8 +164,7 @@ bool HttpClient::RunHttpRequest()
|
|||
|
||||
if (m_loadHeaders)
|
||||
{
|
||||
[response.allHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * obj, BOOL * stop)
|
||||
{
|
||||
[response.allHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * obj, BOOL * stop) {
|
||||
m_headers.emplace(key.lowercaseString.UTF8String, obj.UTF8String);
|
||||
}];
|
||||
}
|
||||
|
@ -199,7 +181,6 @@ bool HttpClient::RunHttpRequest()
|
|||
m_serverResponse.assign(reinterpret_cast<char const *>(url_data.bytes), url_data.length);
|
||||
else
|
||||
[url_data writeToFile:@(m_outputFile.c_str()) atomically:YES];
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -214,9 +195,9 @@ bool HttpClient::RunHttpRequest()
|
|||
}
|
||||
|
||||
m_errorCode = static_cast<int>(err.code);
|
||||
LOG(LDEBUG, ("Error: ", m_errorCode, ':', err.localizedDescription.UTF8String,
|
||||
"while connecting to", m_urlRequested));
|
||||
LOG(LDEBUG,
|
||||
("Error: ", m_errorCode, ':', err.localizedDescription.UTF8String, "while connecting to", m_urlRequested));
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace platform
|
||||
} // namespace om::network::http
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
delegate:(id<NSURLSessionDataDelegate>)delegate
|
||||
completionHandler:(void (^)(NSData * data, NSURLResponse * response,
|
||||
NSError * error))completionHandler;
|
||||
completionHandler:
|
||||
(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler;
|
||||
|
||||
@end
|
|
@ -1,19 +1,17 @@
|
|||
#import "platform/http_session_manager.h"
|
||||
#import "session_manager.h"
|
||||
|
||||
@interface DataTaskInfo : NSObject
|
||||
|
||||
@property(nonatomic, weak) id<NSURLSessionDataDelegate> delegate;
|
||||
@property(nonatomic) NSURLSessionDataTask * task;
|
||||
|
||||
- (instancetype)initWithTask:(NSURLSessionDataTask *)task
|
||||
delegate:(id<NSURLSessionDataDelegate>)delegate;
|
||||
- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DataTaskInfo
|
||||
|
||||
- (instancetype)initWithTask:(NSURLSessionDataTask *)task
|
||||
delegate:(id<NSURLSessionDataDelegate>)delegate
|
||||
- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
|
@ -27,7 +25,7 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface HttpSessionManager ()<NSURLSessionDataDelegate>
|
||||
@interface HttpSessionManager () <NSURLSessionDataDelegate>
|
||||
|
||||
@property(nonatomic) NSURLSession * session;
|
||||
@property(nonatomic) NSMutableDictionary * taskInfoByTaskID;
|
||||
|
@ -73,11 +71,10 @@
|
|||
|
||||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||||
delegate:(id<NSURLSessionDataDelegate>)delegate
|
||||
completionHandler:(void (^)(NSData * data, NSURLResponse * response,
|
||||
NSError * error))completionHandler
|
||||
completionHandler:
|
||||
(void (^)(NSData * data, NSURLResponse * response, NSError * error))completionHandler
|
||||
{
|
||||
NSURLSessionDataTask * task = [self.session dataTaskWithRequest:request
|
||||
completionHandler:completionHandler];
|
||||
NSURLSessionDataTask * task = [self.session dataTaskWithRequest:request completionHandler:completionHandler];
|
||||
|
||||
DataTaskInfo * taskInfo = [[DataTaskInfo alloc] initWithTask:task delegate:delegate];
|
||||
[self setDataTaskInfo:taskInfo forTask:task];
|
||||
|
@ -117,7 +114,7 @@
|
|||
{
|
||||
DataTaskInfo * taskInfo = [self taskInfoForTask:task];
|
||||
if ([taskInfo.delegate
|
||||
respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)])
|
||||
respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)])
|
||||
{
|
||||
dispatch_async(self.delegateQueue, ^{
|
||||
[taskInfo.delegate URLSession:session
|
||||
|
@ -133,9 +130,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(NSError *)error
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
DataTaskInfo * taskInfo = [self taskInfoForTask:task];
|
||||
[self removeTaskInfoForTask:task];
|
||||
|
@ -154,8 +149,7 @@
|
|||
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
{
|
||||
DataTaskInfo * taskInfo = [self taskInfoForTask:dataTask];
|
||||
if ([taskInfo.delegate
|
||||
respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)])
|
||||
if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)])
|
||||
{
|
||||
dispatch_async(self.delegateQueue, ^{
|
||||
[taskInfo.delegate URLSession:session
|
||||
|
@ -170,9 +164,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
|
||||
{
|
||||
DataTaskInfo * taskInfo = [self taskInfoForTask:dataTask];
|
||||
if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)])
|
||||
|
@ -189,8 +181,7 @@
|
|||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential * _Nullable credential))completionHandler
|
||||
{
|
||||
NSURLCredential * credential =
|
||||
[[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
||||
}
|
||||
#endif
|
|
@ -1,9 +1,9 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "platform/socket.hpp"
|
||||
#include "network/socket.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SocketImpl : NSObject
|
||||
|
||||
@property(nonatomic) NSInputStream * inputStream;
|
||||
|
@ -30,15 +30,14 @@
|
|||
CFReadStreamRef readStream;
|
||||
CFWriteStreamRef writeStream;
|
||||
|
||||
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), (UInt32)port, &readStream,
|
||||
&writeStream);
|
||||
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), (UInt32)port, &readStream, &writeStream);
|
||||
|
||||
NSDictionary * settings = @{
|
||||
#ifndef RELEASE
|
||||
(id)kCFStreamSSLValidatesCertificateChain : @NO,
|
||||
(id)kCFStreamSSLValidatesCertificateChain : @NO,
|
||||
#endif
|
||||
(id)kCFStreamSSLLevel : (id)kCFStreamSocketSecurityLevelNegotiatedSSL
|
||||
};
|
||||
(id)kCFStreamSSLLevel : (id)kCFStreamSocketSecurityLevelNegotiatedSSL
|
||||
};
|
||||
|
||||
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
|
||||
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
|
||||
|
@ -152,14 +151,14 @@
|
|||
|
||||
@end
|
||||
|
||||
namespace platform
|
||||
namespace om::network
|
||||
{
|
||||
class PlatformSocket final : public Socket
|
||||
{
|
||||
public:
|
||||
PlatformSocket();
|
||||
// Socket overrides
|
||||
~PlatformSocket();
|
||||
~PlatformSocket() override;
|
||||
bool Open(std::string const & host, uint16_t port) override;
|
||||
void Close() override;
|
||||
bool Read(uint8_t * data, uint32_t count) override;
|
||||
|
@ -170,10 +169,7 @@ private:
|
|||
SocketImpl * m_socketImpl = nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> CreateSocket()
|
||||
{
|
||||
return std::make_unique<PlatformSocket>();
|
||||
}
|
||||
std::unique_ptr<Socket> CreateSocket() { return std::make_unique<PlatformSocket>(); }
|
||||
|
||||
PlatformSocket::PlatformSocket() { m_socketImpl = [[SocketImpl alloc] init]; }
|
||||
|
||||
|
@ -190,15 +186,9 @@ bool PlatformSocket::Open(std::string const & host, uint16_t port)
|
|||
|
||||
void PlatformSocket::Close() { [m_socketImpl close]; }
|
||||
|
||||
bool PlatformSocket::Read(uint8_t * data, uint32_t count)
|
||||
{
|
||||
return [m_socketImpl read:data count:count];
|
||||
}
|
||||
bool PlatformSocket::Read(uint8_t * data, uint32_t count) { return [m_socketImpl read:data count:count]; }
|
||||
|
||||
bool PlatformSocket::Write(uint8_t const * data, uint32_t count)
|
||||
{
|
||||
return [m_socketImpl write:data count:count];
|
||||
}
|
||||
bool PlatformSocket::Write(uint8_t const * data, uint32_t count) { return [m_socketImpl write:data count:count]; }
|
||||
|
||||
void PlatformSocket::SetTimeout(uint32_t milliseconds) { m_socketImpl.timeout = milliseconds; }
|
||||
} // namespace platform
|
||||
} // namespace om::network
|
|
@ -1,21 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "std/target_os.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <string>
|
||||
|
||||
namespace downloader { class IHttpThreadCallback; }
|
||||
#include "network/http/thread.hpp"
|
||||
|
||||
#ifdef OMIM_OS_IPHONE
|
||||
#import "../iphone/Maps/Classes/DownloadIndicatorProtocol.h"
|
||||
#import "../../../../../iphone/Maps/Classes/DownloadIndicatorProtocol.h"
|
||||
#endif
|
||||
|
||||
@interface HttpThreadImpl : NSObject
|
||||
|
||||
- (instancetype)initWithURL:(std::string const &)url
|
||||
callback:(downloader::IHttpThreadCallback &)cb
|
||||
callback:(om::network::http::IThreadCallback &)cb
|
||||
begRange:(int64_t)beg
|
||||
endRange:(int64_t)end
|
||||
expectedSize:(int64_t)size
|
|
@ -1,18 +1,15 @@
|
|||
#import "platform/http_thread_apple.h"
|
||||
|
||||
#include "platform/http_request.hpp"
|
||||
#import "platform/http_session_manager.h"
|
||||
#include "platform/http_thread_callback.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "thread.h"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include "network/internal/native/apple/http/session_manager.h"
|
||||
#include "network/non_http_error_code.hpp"
|
||||
|
||||
static const NSTimeInterval kTimeoutInterval = 10.0;
|
||||
|
||||
@interface HttpThreadImpl ()<NSURLSessionDataDelegate>
|
||||
@interface HttpThreadImpl () <NSURLSessionDataDelegate>
|
||||
{
|
||||
downloader::IHttpThreadCallback * m_callback;
|
||||
om::network::http::IThreadCallback * m_callback;
|
||||
NSURLSessionDataTask * m_dataTask;
|
||||
int64_t m_begRange;
|
||||
int64_t m_endRange;
|
||||
|
@ -20,11 +17,9 @@ static const NSTimeInterval kTimeoutInterval = 10.0;
|
|||
int64_t m_expectedSize;
|
||||
BOOL m_cancelRequested;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation HttpThreadImpl
|
||||
|
||||
#ifdef OMIM_OS_IPHONE
|
||||
static id<DownloadIndicatorProtocol> downloadIndicator = nil;
|
||||
|
||||
|
@ -51,25 +46,24 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
|
|||
}
|
||||
|
||||
- (instancetype)initWithURL:(std::string const &)url
|
||||
callback:(downloader::IHttpThreadCallback &)cb
|
||||
callback:(om::network::http::IThreadCallback &)cb
|
||||
begRange:(int64_t)beg
|
||||
endRange:(int64_t)end
|
||||
expectedSize:(int64_t)size
|
||||
postBody:(std::string const &)pb
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
|
||||
m_callback = &cb;
|
||||
m_begRange = beg;
|
||||
m_endRange = end;
|
||||
m_downloadedBytes = 0;
|
||||
m_expectedSize = size;
|
||||
|
||||
NSMutableURLRequest * request =
|
||||
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@(url.c_str())]
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:kTimeoutInterval];
|
||||
|
||||
|
||||
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@(url.c_str())]
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:kTimeoutInterval];
|
||||
|
||||
// use Range header only if we don't download whole file from start
|
||||
if (!(beg == 0 && end < 0))
|
||||
{
|
||||
|
@ -77,16 +71,16 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
|
|||
if (end > 0)
|
||||
{
|
||||
LOG(LDEBUG, (url, "downloading range [", beg, ",", end, "]"));
|
||||
val = [[NSString alloc] initWithFormat: @"bytes=%qi-%qi", beg, end];
|
||||
val = [[NSString alloc] initWithFormat:@"bytes=%qi-%qi", beg, end];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LDEBUG, (url, "resuming download from position", beg));
|
||||
val = [[NSString alloc] initWithFormat: @"bytes=%qi-", beg];
|
||||
val = [[NSString alloc] initWithFormat:@"bytes=%qi-", beg];
|
||||
}
|
||||
[request addValue:val forHTTPHeaderField:@"Range"];
|
||||
}
|
||||
|
||||
|
||||
if (!pb.empty())
|
||||
{
|
||||
NSData * postData = [NSData dataWithBytes:pb.data() length:pb.size()];
|
||||
|
@ -94,17 +88,15 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
|
|||
[request setHTTPMethod:@"POST"];
|
||||
[request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
}
|
||||
|
||||
|
||||
#ifdef OMIM_OS_IPHONE
|
||||
[downloadIndicator disableStandby];
|
||||
[downloadIndicator enableDownloadIndicator];
|
||||
#endif
|
||||
|
||||
|
||||
// create the task with the request and start loading the data
|
||||
m_dataTask = [[HttpSessionManager sharedManager] dataTaskWithRequest:request
|
||||
delegate:self
|
||||
completionHandler:nil];
|
||||
|
||||
m_dataTask = [[HttpSessionManager sharedManager] dataTaskWithRequest:request delegate:self completionHandler:nil];
|
||||
|
||||
if (m_dataTask)
|
||||
{
|
||||
[m_dataTask resume];
|
||||
|
@ -115,23 +107,22 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
|
|||
LOG(LERROR, ("Can't create data task for", url));
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/// We cancel and don't support any redirects to avoid data corruption
|
||||
/// @TODO Display content to user - router is redirecting us somewhere
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
{
|
||||
LOG(LWARNING, ("Canceling because of redirect from", response.URL.absoluteString.UTF8String, "to",
|
||||
request.URL.absoluteString.UTF8String));
|
||||
completionHandler(nil);
|
||||
m_callback->OnFinish(static_cast<NSHTTPURLResponse *>(response).statusCode, m_begRange,
|
||||
m_endRange);
|
||||
m_callback->OnFinish(static_cast<NSHTTPURLResponse *>(response).statusCode, m_begRange, m_endRange);
|
||||
}
|
||||
|
||||
/// @return -1 if can't decode
|
||||
|
@ -143,18 +134,18 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|||
if ([arr count])
|
||||
return [(NSString *)[arr objectAtIndex:[arr count] - 1] longLongValue];
|
||||
}
|
||||
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||||
{
|
||||
// This method is called when the server has determined that it
|
||||
// has enough information to create the NSURLResponse.
|
||||
|
||||
|
||||
// check if this is OK (not a 404 or the like)
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]])
|
||||
{
|
||||
|
@ -180,14 +171,13 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// We should always check returned size, even if it's invalid (-1)
|
||||
if (m_expectedSize != sizeOnServer)
|
||||
{
|
||||
LOG(LWARNING, ("Canceling download - server replied with invalid size", sizeOnServer,
|
||||
"!=", m_expectedSize));
|
||||
LOG(LWARNING, ("Canceling download - server replied with invalid size", sizeOnServer, "!=", m_expectedSize));
|
||||
completionHandler(NSURLSessionResponseCancel);
|
||||
m_callback->OnFinish(downloader::non_http_error_code::kInconsistentFileSize, m_begRange, m_endRange);
|
||||
m_callback->OnFinish(om::network::non_http_error_code::kInconsistentFileSize, m_begRange, m_endRange);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
else
|
||||
|
@ -195,65 +185,71 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// In theory, we should never be here.
|
||||
ASSERT(false, ("Invalid non-http response, aborting request"));
|
||||
completionHandler(NSURLSessionResponseCancel);
|
||||
m_callback->OnFinish(downloader::non_http_error_code::kNonHttpResponse, m_begRange, m_endRange);
|
||||
m_callback->OnFinish(om::network::non_http_error_code::kNonHttpResponse, m_begRange, m_endRange);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
|
||||
{
|
||||
int64_t const length = [data length];
|
||||
m_downloadedBytes += length;
|
||||
if(!m_callback->OnWrite(m_begRange + m_downloadedBytes - length, [data bytes], length))
|
||||
if (!m_callback->OnWrite(m_begRange + m_downloadedBytes - length, [data bytes], length))
|
||||
{
|
||||
[m_dataTask cancel];
|
||||
m_callback->OnFinish(downloader::non_http_error_code::kWriteException, m_begRange, m_endRange);
|
||||
m_callback->OnFinish(om::network::non_http_error_code::kWriteException, m_begRange, m_endRange);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(NSError *)error
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
if (error.code == NSURLErrorCancelled || m_cancelRequested)
|
||||
return;
|
||||
|
||||
|
||||
if (error)
|
||||
m_callback->OnFinish([error code], m_begRange, m_endRange);
|
||||
else
|
||||
m_callback->OnFinish(200, m_begRange, m_endRange);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
class HttpThread
|
||||
namespace om::network::http
|
||||
{
|
||||
public:
|
||||
HttpThread(HttpThreadImpl * request)
|
||||
: m_request(request)
|
||||
{}
|
||||
class Thread
|
||||
{
|
||||
friend void thread::DeleteThread(Thread * thread);
|
||||
|
||||
HttpThreadImpl * m_request;
|
||||
public:
|
||||
Thread(HttpThreadImpl * thread) : m_thread(thread) {}
|
||||
|
||||
#ifdef OMIM_OS_IPHONE
|
||||
static void setDownloadIndicatorProtocol(id<DownloadIndicatorProtocol> indicator)
|
||||
{
|
||||
[HttpThreadImpl setDownloadIndicatorProtocol:indicator];
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
HttpThreadImpl * m_thread;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace downloader
|
||||
namespace thread
|
||||
{
|
||||
HttpThread * CreateNativeHttpThread(std::string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t size,
|
||||
std::string const & pb)
|
||||
Thread * CreateThread(std::string const & url, IThreadCallback & cb, int64_t beg, int64_t end, int64_t size,
|
||||
std::string const & pb)
|
||||
{
|
||||
HttpThreadImpl * request = [[HttpThreadImpl alloc] initWithURL:url callback:cb begRange:beg endRange:end expectedSize:size postBody:pb];
|
||||
return new HttpThread(request);
|
||||
HttpThreadImpl * thread = [[HttpThreadImpl alloc] initWithURL:url
|
||||
callback:cb
|
||||
begRange:beg
|
||||
endRange:end
|
||||
expectedSize:size
|
||||
postBody:pb];
|
||||
return new Thread(thread);
|
||||
}
|
||||
|
||||
void DeleteNativeHttpThread(HttpThread * request)
|
||||
void DeleteThread(Thread * thread)
|
||||
{
|
||||
[request->m_request cancel];
|
||||
delete request;
|
||||
[thread->m_thread cancel];
|
||||
delete thread;
|
||||
}
|
||||
} // namespace downloader
|
||||
} // namespace thread
|
||||
} // namespace om::network::http
|
|
@ -1,14 +1,12 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#include "network/http/uploader.hpp"
|
||||
|
||||
#include "platform/http_uploader.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/waiter.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "private.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <memory>
|
||||
|
||||
@interface IdentityAndTrust : NSObject
|
||||
|
||||
@property(nonatomic) SecIdentityRef identityRef;
|
||||
|
@ -26,7 +24,7 @@
|
|||
_identityRef = (SecIdentityRef)CFRetain(identity);
|
||||
_certArray = (__bridge_transfer NSArray *)CFRetain(certs);
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -37,7 +35,7 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface MultipartUploadTask : NSObject<NSURLSessionDelegate>
|
||||
@interface MultipartUploadTask : NSObject <NSURLSessionDelegate>
|
||||
|
||||
@property(copy, nonatomic) NSString * method;
|
||||
@property(copy, nonatomic) NSString * urlString;
|
||||
|
@ -50,39 +48,35 @@
|
|||
|
||||
@implementation MultipartUploadTask
|
||||
|
||||
- (NSData *)requestDataWithBoundary:(NSString *)boundary {
|
||||
- (NSData *)requestDataWithBoundary:(NSString *)boundary
|
||||
{
|
||||
NSMutableData * data = [NSMutableData data];
|
||||
|
||||
[self.params enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * value, BOOL * stop) {
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",
|
||||
key] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"%@\r\n", value]
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}];
|
||||
|
||||
NSString * fileName = self.filePath.lastPathComponent;
|
||||
NSData * fileData = [NSData dataWithContentsOfFile:self.filePath];
|
||||
NSString * mimeType = @"application/octet-stream";
|
||||
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",
|
||||
self.fileKey,
|
||||
fileName]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
self.fileKey, fileName] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:fileData];
|
||||
[data appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@--", boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[data appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (void)uploadWithCompletion:(void (^)(NSInteger httpCode, NSString *description))completion {
|
||||
- (void)uploadWithCompletion:(void (^)(NSInteger httpCode, NSString * description))completion
|
||||
{
|
||||
NSURL * url = [NSURL URLWithString:self.urlString];
|
||||
NSMutableURLRequest * uploadRequest = [NSMutableURLRequest requestWithURL:url];
|
||||
uploadRequest.timeoutInterval = 15;
|
||||
|
@ -91,7 +85,7 @@
|
|||
NSString * boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
|
||||
NSString * contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
|
||||
[uploadRequest setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
|
||||
[self.headers enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * value, BOOL * stop) {
|
||||
[uploadRequest setValue:value forHTTPHeaderField:key];
|
||||
}];
|
||||
|
@ -102,21 +96,21 @@
|
|||
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
|
||||
delegate:self
|
||||
delegateQueue:nil];
|
||||
NSURLSessionUploadTask * uploadTask = [session
|
||||
uploadTaskWithRequest:uploadRequest
|
||||
fromData:postData
|
||||
completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
|
||||
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (error == nil)
|
||||
{
|
||||
NSString * description = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
completion(httpResponse.statusCode, description);
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(-1, error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
NSURLSessionUploadTask * uploadTask =
|
||||
[session uploadTaskWithRequest:uploadRequest
|
||||
fromData:postData
|
||||
completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
|
||||
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (error == nil)
|
||||
{
|
||||
NSString * description = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
completion(httpResponse.statusCode, description);
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(-1, error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
|
@ -133,8 +127,7 @@
|
|||
else if (authenticationMethod == NSURLAuthenticationMethodServerTrust)
|
||||
{
|
||||
#if DEBUG
|
||||
NSURLCredential * credential =
|
||||
[[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
||||
#else
|
||||
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
|
||||
|
@ -145,19 +138,16 @@
|
|||
- (NSURLCredential *)getClientUrlCredential
|
||||
{
|
||||
NSData * certData = [[NSData alloc] initWithBase64EncodedString:@USER_BINDING_PKCS12 options:0];
|
||||
IdentityAndTrust * identity = [self extractIdentityWithCertData:certData
|
||||
certPassword:@USER_BINDING_PKCS12_PASSWORD];
|
||||
IdentityAndTrust * identity = [self extractIdentityWithCertData:certData certPassword:@USER_BINDING_PKCS12_PASSWORD];
|
||||
|
||||
NSURLCredential * urlCredential =
|
||||
[[NSURLCredential alloc] initWithIdentity:identity.identityRef
|
||||
certificates:identity.certArray
|
||||
persistence:NSURLCredentialPersistenceForSession];
|
||||
NSURLCredential * urlCredential = [[NSURLCredential alloc] initWithIdentity:identity.identityRef
|
||||
certificates:identity.certArray
|
||||
persistence:NSURLCredentialPersistenceForSession];
|
||||
|
||||
return urlCredential;
|
||||
}
|
||||
|
||||
- (IdentityAndTrust *)extractIdentityWithCertData:(NSData *)certData
|
||||
certPassword:(NSString *)certPassword
|
||||
- (IdentityAndTrust *)extractIdentityWithCertData:(NSData *)certData certPassword:(NSString *)certPassword
|
||||
{
|
||||
IdentityAndTrust * identityAndTrust;
|
||||
|
||||
|
@ -165,17 +155,16 @@
|
|||
|
||||
CFArrayRef items = nullptr;
|
||||
OSStatus status = SecPKCS12Import((CFDataRef)certData, (CFDictionaryRef)certOptions, &items);
|
||||
|
||||
|
||||
if (status == errSecSuccess && CFArrayGetCount(items) > 0)
|
||||
{
|
||||
CFDictionaryRef firstItem = (CFDictionaryRef)CFArrayGetValueAtIndex(items, 0);
|
||||
|
||||
|
||||
CFTypeRef identityRef = CFDictionaryGetValue(firstItem, kSecImportItemIdentity);
|
||||
CFTypeRef certChainRef = CFDictionaryGetValue(firstItem, kSecImportItemCertChain);
|
||||
|
||||
identityAndTrust = [[IdentityAndTrust alloc] initWithIdentity:identityRef
|
||||
certChain:certChainRef];
|
||||
|
||||
identityAndTrust = [[IdentityAndTrust alloc] initWithIdentity:identityRef certChain:certChainRef];
|
||||
|
||||
CFRelease(items);
|
||||
}
|
||||
|
||||
|
@ -184,15 +173,14 @@
|
|||
|
||||
@end
|
||||
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
HttpUploader::Result HttpUploader::Upload() const
|
||||
Uploader::Result Uploader::Upload() const
|
||||
{
|
||||
std::shared_ptr<Result> resultPtr = std::make_shared<Result>();
|
||||
std::shared_ptr<base::Waiter> waiterPtr = std::make_shared<base::Waiter>();
|
||||
|
||||
auto mapTransform =
|
||||
^NSDictionary<NSString *, NSString *> * (std::map<std::string, std::string> keyValues)
|
||||
auto mapTransform = ^NSDictionary<NSString *, NSString *> *(std::map<std::string, std::string> keyValues)
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSString *> * params = [NSMutableDictionary dictionary];
|
||||
for (auto const & keyValue : keyValues)
|
||||
|
@ -207,12 +195,13 @@ HttpUploader::Result HttpUploader::Upload() const
|
|||
uploadTask.filePath = @(m_payload.m_filePath.c_str());
|
||||
uploadTask.params = mapTransform(m_payload.m_params);
|
||||
uploadTask.headers = mapTransform(m_payload.m_headers);
|
||||
[uploadTask uploadWithCompletion:[resultPtr, waiterPtr](NSInteger httpCode, NSString * description) {
|
||||
resultPtr->m_httpCode = static_cast<int32_t>(httpCode);
|
||||
resultPtr->m_description = description.UTF8String;
|
||||
waiterPtr->Notify();
|
||||
}];
|
||||
[uploadTask uploadWithCompletion:[resultPtr, waiterPtr](NSInteger httpCode, NSString * description)
|
||||
{
|
||||
resultPtr->m_httpCode = static_cast<int32_t>(httpCode);
|
||||
resultPtr->m_description = description.UTF8String;
|
||||
waiterPtr->Notify();
|
||||
}];
|
||||
waiterPtr->Wait();
|
||||
return *resultPtr;
|
||||
}
|
||||
} // namespace platform
|
||||
} // namespace om::network::http
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/network_policy.hpp"
|
||||
#include "network/network_policy.hpp"
|
||||
|
||||
@class NSDate;
|
||||
|
||||
namespace network_policy
|
||||
namespace om::network::network_policy
|
||||
{
|
||||
enum Stage
|
||||
{
|
||||
|
@ -20,5 +20,5 @@ Stage GetStage();
|
|||
|
||||
bool CanUseNetwork();
|
||||
bool IsActivePolicyDate();
|
||||
NSDate* GetPolicyDate();
|
||||
} // namespace network_policy
|
||||
NSDate * GetPolicyDate();
|
||||
} // namespace om::network::network_policy
|
|
@ -1,4 +1,4 @@
|
|||
#include "platform/network_policy_ios.h"
|
||||
#include "network_policy.h"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
|
@ -13,29 +13,22 @@ NSString * const kNetworkingPolicyStage = @"NetworkingPolicyStage";
|
|||
NSTimeInterval const kSessionDurationSeconds = 24 * 60 * 60;
|
||||
} // namespace
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
namespace network_policy
|
||||
{
|
||||
void SetStage(Stage stage)
|
||||
{
|
||||
NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;
|
||||
NSUserDefaults * ud = NSUserDefaults.standardUserDefaults;
|
||||
[ud setInteger:stage forKey:kNetworkingPolicyStage];
|
||||
[ud setObject:[NSDate dateWithTimeIntervalSinceNow:kSessionDurationSeconds] forKey:kNetworkingPolicyTimeStamp];
|
||||
}
|
||||
|
||||
Stage GetStage()
|
||||
{
|
||||
return (Stage)[NSUserDefaults.standardUserDefaults integerForKey:kNetworkingPolicyStage];
|
||||
}
|
||||
Stage GetStage() { return (Stage)[NSUserDefaults.standardUserDefaults integerForKey:kNetworkingPolicyStage]; }
|
||||
|
||||
NSDate * GetPolicyDate()
|
||||
{
|
||||
return [NSUserDefaults.standardUserDefaults objectForKey:kNetworkingPolicyTimeStamp];
|
||||
}
|
||||
NSDate * GetPolicyDate() { return [NSUserDefaults.standardUserDefaults objectForKey:kNetworkingPolicyTimeStamp]; }
|
||||
|
||||
bool IsActivePolicyDate()
|
||||
{
|
||||
return [GetPolicyDate() compare:[NSDate date]] == NSOrderedDescending;
|
||||
}
|
||||
bool IsActivePolicyDate() { return [GetPolicyDate() compare:[NSDate date]] == NSOrderedDescending; }
|
||||
|
||||
bool CanUseNetwork()
|
||||
{
|
||||
|
@ -57,10 +50,5 @@ bool CanUseNetwork()
|
|||
}
|
||||
} // namespace network_policy
|
||||
|
||||
namespace platform
|
||||
{
|
||||
NetworkPolicy GetCurrentNetworkPolicy()
|
||||
{
|
||||
return NetworkPolicy(network_policy::CanUseNetwork());
|
||||
}
|
||||
} // namespace platform
|
||||
NetworkPolicy GetCurrentNetworkPolicy() { return NetworkPolicy(network_policy::CanUseNetwork()); }
|
||||
} // namespace om::network
|
|
@ -1,27 +1,5 @@
|
|||
/*******************************************************************************
|
||||
The MIT License (MIT)
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
Copyright (c) 2014 Alexander Borsuk <me@alex.bio> from Minsk, Belarus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*******************************************************************************/
|
||||
#include "platform/http_client.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/zlib.hpp"
|
||||
|
@ -31,10 +9,10 @@
|
|||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdio> // popen, tmpnam
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
|
@ -42,8 +20,6 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdio> // popen, tmpnam
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
|
@ -80,7 +56,6 @@ static std::string ReadFileAsString(std::string const & filePath)
|
|||
return {std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
|
||||
std::string RunCurl(std::string const & cmd)
|
||||
{
|
||||
FILE * pipe = ::popen(cmd.c_str(), "r");
|
||||
|
@ -141,7 +116,7 @@ HeadersVector ParseHeaders(std::string const & raw)
|
|||
bool WriteToFile(std::string const & fileName, std::string const & data)
|
||||
{
|
||||
std::ofstream ofs(fileName);
|
||||
if(!ofs.is_open())
|
||||
if (!ofs.is_open())
|
||||
{
|
||||
LOG(LERROR, ("Failed to write into a temporary file."));
|
||||
return false;
|
||||
|
@ -177,12 +152,12 @@ std::string Decompress(std::string const & compressed, std::string const & encod
|
|||
// Used as a test stub for basic HTTP client implementation.
|
||||
// Make sure that you have curl installed in the PATH.
|
||||
// TODO(AlexZ): Not a production-ready implementation.
|
||||
namespace platform
|
||||
namespace om::network::http
|
||||
{
|
||||
// Extract HTTP headers via temporary file with -D switch.
|
||||
// HTTP status code is extracted from curl output (-w switches).
|
||||
// Redirects are handled recursively. TODO(AlexZ): avoid infinite redirects loop.
|
||||
bool HttpClient::RunHttpRequest()
|
||||
bool Client::RunHttpRequest()
|
||||
{
|
||||
ScopedRemoveFile headers_deleter(GetTmpFileName());
|
||||
ScopedRemoveFile body_deleter;
|
||||
|
@ -284,7 +259,7 @@ bool HttpClient::RunHttpRequest()
|
|||
// TODO(AlexZ): Should we check HTTP redirect code here?
|
||||
LOG(LDEBUG, ("HTTP redirect", m_errorCode, "to", m_urlReceived));
|
||||
|
||||
HttpClient redirect(m_urlReceived);
|
||||
Client redirect(m_urlReceived);
|
||||
redirect.SetCookies(CombinedCookies());
|
||||
|
||||
if (!redirect.RunHttpRequest())
|
||||
|
@ -301,8 +276,7 @@ bool HttpClient::RunHttpRequest()
|
|||
|
||||
for (auto const & header : headers)
|
||||
{
|
||||
if (strings::EqualNoCase(header.first, "content-encoding") &&
|
||||
!strings::EqualNoCase(header.second, "identity"))
|
||||
if (strings::EqualNoCase(header.first, "content-encoding") && !strings::EqualNoCase(header.second, "identity"))
|
||||
{
|
||||
m_serverResponse = Decompress(m_serverResponse, header.second);
|
||||
LOG(LDEBUG, ("Response with", header.second, "is decompressed."));
|
||||
|
@ -311,4 +285,4 @@ bool HttpClient::RunHttpRequest()
|
|||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace platform
|
||||
} // namespace om::network::http
|
|
@ -1,24 +1,16 @@
|
|||
#include "platform/http_thread_qt.hpp"
|
||||
|
||||
#include "platform/http_thread_callback.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "thread.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QSslError>
|
||||
#include <QUrl>
|
||||
|
||||
using namespace std;
|
||||
|
||||
HttpThread::HttpThread(string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t size,
|
||||
string const & pb)
|
||||
namespace om::network::http
|
||||
{
|
||||
Thread::Thread(string const & url, IThreadCallback & cb, int64_t beg, int64_t end, int64_t size, string const & pb)
|
||||
: m_callback(cb), m_begRange(beg), m_endRange(end), m_downloadedBytes(0), m_expectedSize(size)
|
||||
{
|
||||
QUrl const qUrl(url.c_str());
|
||||
|
@ -30,8 +22,7 @@ HttpThread::HttpThread(string const & url,
|
|||
if (end > 0)
|
||||
{
|
||||
LOG(LDEBUG, (url, "downloading range [", beg, ",", end, "]"));
|
||||
QString const range = QString("bytes=") + QString::number(beg)
|
||||
+ '-' + QString::number(end);
|
||||
QString const range = QString("bytes=") + QString::number(beg) + '-' + QString::number(end);
|
||||
request.setRawHeader("Range", range.toUtf8());
|
||||
}
|
||||
else
|
||||
|
@ -60,7 +51,7 @@ HttpThread::HttpThread(string const & url,
|
|||
LOG(LDEBUG, ("Connecting to", url, "[", beg, ",", end, "]", "size=", size));
|
||||
}
|
||||
|
||||
HttpThread::~HttpThread()
|
||||
Thread::~Thread()
|
||||
{
|
||||
LOG(LDEBUG, ("Destroying HttpThread"));
|
||||
disconnect(m_reply, SIGNAL(metaDataChanged()), this, SLOT(OnHeadersReceived()));
|
||||
|
@ -70,7 +61,7 @@ HttpThread::~HttpThread()
|
|||
m_reply->deleteLater();
|
||||
}
|
||||
|
||||
void HttpThread::OnHeadersReceived()
|
||||
void Thread::OnHeadersReceived()
|
||||
{
|
||||
// We don't notify our callback here, because OnDownloadFinished() will always be called
|
||||
// Note: after calling reply->abort() all other reply's members calls crash the app
|
||||
|
@ -83,7 +74,8 @@ void HttpThread::OnHeadersReceived()
|
|||
bool const isChunk = !(m_begRange == 0 && m_endRange < 0);
|
||||
if ((isChunk && httpStatusCode != 206) || (!isChunk && httpStatusCode != 200))
|
||||
{
|
||||
LOG(LWARNING, ("Http request to", m_reply->url().toEncoded().constData(), "aborted with HTTP code", httpStatusCode));
|
||||
LOG(LWARNING,
|
||||
("Http request to", m_reply->url().toEncoded().constData(), "aborted with HTTP code", httpStatusCode));
|
||||
m_reply->abort();
|
||||
}
|
||||
else if (m_expectedSize > 0)
|
||||
|
@ -96,7 +88,7 @@ void HttpThread::OnHeadersReceived()
|
|||
if (numElements && contentRange.at(numElements - 1).toLongLong() != m_expectedSize)
|
||||
{
|
||||
LOG(LWARNING, ("Http request to", m_reply->url().toEncoded().constData(),
|
||||
"aborted - invalid Content-Range:", contentRange.at(numElements - 1).toLongLong()));
|
||||
"aborted - invalid Content-Range:", contentRange.at(numElements - 1).toLongLong()));
|
||||
m_reply->abort();
|
||||
}
|
||||
}
|
||||
|
@ -107,20 +99,20 @@ void HttpThread::OnHeadersReceived()
|
|||
if (expSize != m_expectedSize)
|
||||
{
|
||||
LOG(LWARNING, ("Http request to", m_reply->url().toEncoded().constData(),
|
||||
"aborted - invalid Content-Length:", m_reply->rawHeader("Content-Length").toLongLong()));
|
||||
"aborted - invalid Content-Length:", m_reply->rawHeader("Content-Length").toLongLong()));
|
||||
m_reply->abort();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LWARNING, ("Http request to", m_reply->url().toEncoded().constData(),
|
||||
"aborted, server didn't send any valid file size"));
|
||||
"aborted, server didn't send any valid file size"));
|
||||
m_reply->abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpThread::OnChunkDownloaded()
|
||||
void Thread::OnChunkDownloaded()
|
||||
{
|
||||
QByteArray const data = m_reply->readAll();
|
||||
int const chunkSize = data.size();
|
||||
|
@ -128,14 +120,12 @@ void HttpThread::OnChunkDownloaded()
|
|||
m_callback.OnWrite(m_begRange + m_downloadedBytes - chunkSize, data.constData(), chunkSize);
|
||||
}
|
||||
|
||||
void HttpThread::OnDownloadFinished()
|
||||
void Thread::OnDownloadFinished()
|
||||
{
|
||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError)
|
||||
{
|
||||
auto const httpStatusCode =
|
||||
m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
LOG(LWARNING, ("Download has finished with code:", httpStatusCode,
|
||||
"error:", m_reply->errorString().toStdString()));
|
||||
auto const httpStatusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
LOG(LWARNING, ("Download has finished with code:", httpStatusCode, "error:", m_reply->errorString().toStdString()));
|
||||
m_callback.OnFinish(httpStatusCode, m_begRange, m_endRange);
|
||||
}
|
||||
else
|
||||
|
@ -144,21 +134,14 @@ void HttpThread::OnDownloadFinished()
|
|||
}
|
||||
}
|
||||
|
||||
namespace downloader
|
||||
namespace thread
|
||||
{
|
||||
|
||||
HttpThread * CreateNativeHttpThread(string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t size,
|
||||
string const & pb)
|
||||
Thread * CreateThread(std::string const & url, IThreadCallback & cb, int64_t beg, int64_t end, int64_t size,
|
||||
std::string const & pb)
|
||||
{
|
||||
return new HttpThread(url, cb, beg, end, size, pb);
|
||||
return new Thread(url, cb, beg, end, size, pb);
|
||||
}
|
||||
|
||||
void DeleteNativeHttpThread(HttpThread * request)
|
||||
{
|
||||
delete request;
|
||||
}
|
||||
} // namespace downloader
|
||||
void DeleteThread(Thread * thread) { delete thread; }
|
||||
} // namespace thread
|
||||
} // namespace om::network::http
|
31
network/internal/native/desktop/http/thread.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "network/http/thread.hpp"
|
||||
|
||||
namespace om::network::http
|
||||
{
|
||||
class Thread : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Thread(std::string const & url, IThreadCallback & cb, int64_t beg, int64_t end, int64_t size, std::string const & pb);
|
||||
~Thread() override;
|
||||
|
||||
private slots:
|
||||
void OnHeadersReceived();
|
||||
void OnChunkDownloaded();
|
||||
void OnDownloadFinished();
|
||||
|
||||
private:
|
||||
IThreadCallback & m_callback;
|
||||
QNetworkReply * m_reply;
|
||||
int64_t m_begRange;
|
||||
int64_t m_endRange;
|
||||
int64_t m_downloadedBytes;
|
||||
int64_t m_expectedSize;
|
||||
};
|
||||
} // namespace om::network::http
|
17
network/network_policy.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
class NetworkPolicy
|
||||
{
|
||||
public:
|
||||
explicit NetworkPolicy(bool const canUseNetwork) : m_canUse(canUseNetwork) {}
|
||||
|
||||
bool CanUse() const { return m_canUse; }
|
||||
|
||||
private:
|
||||
bool m_canUse = false;
|
||||
};
|
||||
|
||||
NetworkPolicy GetCurrentNetworkPolicy();
|
||||
} // namespace om::network
|
21
network/network_tests/CMakeLists.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
project(network_tests)
|
||||
|
||||
set(SRC
|
||||
downloader/chunks_downloader.cpp
|
||||
downloader/simple_downloader.cpp
|
||||
downloader/utils.cpp
|
||||
chunks_download_strategy.cpp
|
||||
servers_list.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_SERVER GTEST)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
network
|
||||
network_tests_support
|
||||
platform
|
||||
GTest::gtest_main
|
||||
gmock
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
)
|
111
network/network_tests/chunks_download_strategy.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include "network/chunks_download_strategy.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
using namespace ::testing;
|
||||
|
||||
using RangeT = std::pair<int64_t, int64_t>;
|
||||
|
||||
TEST(ChunksDownloadStrategy, Success)
|
||||
{
|
||||
std::vector<std::string> const servers = {"UrlOfServer1", "UrlOfServer2", "UrlOfServer3"};
|
||||
|
||||
RangeT const R1{0, 249}, R2{250, 499}, R3{500, 749}, R4{750, 800};
|
||||
|
||||
int64_t constexpr kFileSize = 800;
|
||||
int64_t constexpr kChunkSize = 250;
|
||||
ChunksDownloadStrategy strategy(servers);
|
||||
strategy.InitChunks(kFileSize, kChunkSize);
|
||||
|
||||
std::string s1;
|
||||
RangeT r1;
|
||||
EXPECT_EQ(strategy.NextChunk(s1, r1), ChunksDownloadStrategy::ENextChunk);
|
||||
|
||||
std::string s2;
|
||||
RangeT r2;
|
||||
EXPECT_EQ(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENextChunk);
|
||||
|
||||
std::string s3;
|
||||
RangeT r3;
|
||||
EXPECT_EQ(strategy.NextChunk(s3, r3), ChunksDownloadStrategy::ENextChunk);
|
||||
|
||||
std::string sEmpty;
|
||||
RangeT rEmpty;
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
EXPECT_TRUE(s1 != s2 && s2 != s3 && s3 != s1) << s1 << " " << s2 << " " << s3;
|
||||
|
||||
EXPECT_TRUE(r1 != r2 && r2 != r3 && r3 != r1)
|
||||
<< PrintToString(r1) << " " << PrintToString(r2) << " " << PrintToString(r3);
|
||||
EXPECT_TRUE(r1 == R1 || r1 == R2 || r1 == R3 || r1 == R4) << PrintToString(r1);
|
||||
EXPECT_TRUE(r2 == R1 || r2 == R2 || r2 == R3 || r2 == R4) << PrintToString(r2);
|
||||
EXPECT_TRUE(r3 == R1 || r3 == R2 || r3 == R3 || r3 == R4) << PrintToString(r3);
|
||||
|
||||
strategy.ChunkFinished(true, r1);
|
||||
|
||||
std::string s4;
|
||||
RangeT r4;
|
||||
EXPECT_EQ(strategy.NextChunk(s4, r4), ChunksDownloadStrategy::ENextChunk);
|
||||
EXPECT_EQ(s4, s1);
|
||||
EXPECT_TRUE(r4 != r1 && r4 != r2 && r4 != r3) << PrintToString(r4);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(false, r2);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(true, r4);
|
||||
|
||||
std::string s5;
|
||||
RangeT r5;
|
||||
EXPECT_EQ(strategy.NextChunk(s5, r5), ChunksDownloadStrategy::ENextChunk);
|
||||
EXPECT_EQ(s5, s4) << PrintToString(s5) << " " << PrintToString(s4);
|
||||
EXPECT_EQ(r5, r2);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(true, r5);
|
||||
|
||||
// 3rd is still alive here
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(true, r3);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::EDownloadSucceeded);
|
||||
EXPECT_EQ(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::EDownloadSucceeded);
|
||||
}
|
||||
|
||||
TEST(ChunksDownloadStrategy, Fail)
|
||||
{
|
||||
std::vector<std::string> const servers = {"UrlOfServer1", "UrlOfServer2"};
|
||||
|
||||
int64_t constexpr kFileSize = 800;
|
||||
int64_t constexpr kChunkSize = 250;
|
||||
ChunksDownloadStrategy strategy(servers);
|
||||
strategy.InitChunks(kFileSize, kChunkSize);
|
||||
|
||||
std::string s1;
|
||||
RangeT r1;
|
||||
EXPECT_EQ(strategy.NextChunk(s1, r1), ChunksDownloadStrategy::ENextChunk);
|
||||
|
||||
std::string s2;
|
||||
RangeT r2;
|
||||
EXPECT_EQ(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENextChunk);
|
||||
EXPECT_EQ(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(false, r1);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENoFreeServers);
|
||||
|
||||
strategy.ChunkFinished(false, r2);
|
||||
|
||||
EXPECT_EQ(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::EDownloadFailed);
|
||||
}
|
||||
} // namespace om::network
|
253
network/network_tests/downloader/chunks_downloader.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "network/chunks_download_strategy.hpp"
|
||||
#include "network/network_tests_support/request_fixture.hpp"
|
||||
|
||||
namespace om::network::downloader::testing
|
||||
{
|
||||
using namespace ::testing;
|
||||
using namespace om::network::testing;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Should match file size in tools/python/ResponseProvider.py
|
||||
int constexpr kBigFileSize = 47684;
|
||||
} // namespace
|
||||
|
||||
class ChunksDownloaderTests : public RequestFixture
|
||||
{
|
||||
public:
|
||||
void TearDown() override { DeleteTempDownloadedFiles(); }
|
||||
|
||||
using RequestFixture::MakeFileRequest;
|
||||
|
||||
template <class Functor>
|
||||
auto MakeFileRequest(std::vector<std::string> const & urls, std::string const & fileName, int64_t fileSize,
|
||||
Functor & f)
|
||||
{
|
||||
return http::Request::GetFile(
|
||||
urls, fileName, fileSize, [&f](http::Request & request) { f.OnDownloadFinish(request); },
|
||||
[&f](http::Request & request) { f.OnDownloadProgress(request); });
|
||||
}
|
||||
|
||||
void ExpectFileDownloadSuccessAndClear()
|
||||
{
|
||||
ExpectOK();
|
||||
|
||||
EXPECT_TRUE(base::DeleteFileX(m_fileName)) << "Result file should present on success";
|
||||
|
||||
uint64_t size;
|
||||
EXPECT_FALSE(base::GetFileSize(m_fileName + DOWNLOADING_FILE_EXTENSION, size)) << "No downloading file on success";
|
||||
EXPECT_FALSE(base::GetFileSize(m_fileName + RESUME_FILE_EXTENSION, size)) << "No resume file on success";
|
||||
}
|
||||
|
||||
void ExpectFileDownloadFailAndClear()
|
||||
{
|
||||
ExpectFail();
|
||||
|
||||
uint64_t size;
|
||||
EXPECT_FALSE(base::GetFileSize(m_fileName, size)) << "No result file on fail";
|
||||
UNUSED_VALUE(base::DeleteFileX(m_fileName + DOWNLOADING_FILE_EXTENSION));
|
||||
EXPECT_TRUE(base::DeleteFileX(m_fileName + RESUME_FILE_EXTENSION)) << "Resume file should present on fail";
|
||||
}
|
||||
|
||||
protected:
|
||||
static std::string ReadFileAsString(std::string const & file)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileReader f(file);
|
||||
std::string s;
|
||||
f.ReadAsString(s);
|
||||
return s;
|
||||
}
|
||||
catch (FileReader::Exception const &)
|
||||
{
|
||||
EXPECT_TRUE(false) << "File " << file << " should exist";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteTempDownloadedFiles()
|
||||
{
|
||||
FileWriter::DeleteFileX(m_fileName + RESUME_FILE_EXTENSION);
|
||||
FileWriter::DeleteFileX(m_fileName + DOWNLOADING_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
static std::string GetFileNameForTest()
|
||||
{
|
||||
const auto testInfo = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
const std::string testSuiteName{testInfo->test_suite_name()};
|
||||
const std::string testName{testInfo->name()};
|
||||
return Platform().TmpPathForFile(testSuiteName + "_" + testName);
|
||||
}
|
||||
|
||||
std::string m_fileName = GetFileNameForTest();
|
||||
};
|
||||
|
||||
TEST_F(ChunksDownloaderTests, OneThreadSuccess)
|
||||
{
|
||||
const std::vector<std::string> urls = {kTestUrl1, kTestUrl1};
|
||||
const int64_t fileSize = 5;
|
||||
|
||||
// should use only one thread
|
||||
std::unique_ptr<http::Request> const request{MakeFileRequest(urls, m_fileName, fileSize, 512 * 1024)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
EXPECT_EQ(request->GetData(), m_fileName);
|
||||
EXPECT_EQ(ReadFileAsString(m_fileName), "Test1");
|
||||
ExpectFileDownloadSuccessAndClear();
|
||||
}
|
||||
|
||||
TEST_F(ChunksDownloaderTests, ThreeThreadsSuccess)
|
||||
{
|
||||
const std::vector<std::string> urls = {kTestUrlBigFile, kTestUrlBigFile, kTestUrlBigFile};
|
||||
const int64_t fileSize = kBigFileSize;
|
||||
|
||||
// should use only one thread
|
||||
std::unique_ptr<http::Request> const request{MakeFileRequest(urls, m_fileName, fileSize, 2048)};
|
||||
UNUSED_VALUE(request);
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFileDownloadSuccessAndClear();
|
||||
}
|
||||
|
||||
TEST_F(ChunksDownloaderTests, ThreeThreadsFail)
|
||||
{
|
||||
const std::vector<std::string> urls = {kTestUrlBigFile, kTestUrlBigFile, kTestUrlBigFile};
|
||||
const int64_t fileSize = 5;
|
||||
|
||||
// should use only one thread
|
||||
std::unique_ptr<http::Request> const request{MakeFileRequest(urls, m_fileName, fileSize, 2048)};
|
||||
UNUSED_VALUE(request);
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFileDownloadFailAndClear();
|
||||
}
|
||||
|
||||
TEST_F(ChunksDownloaderTests, ThreeThreadsWithOnlyOneValidURLSuccess)
|
||||
{
|
||||
const std::vector<std::string> urls = {kTestUrlBigFile, kTestUrl1, kTestUrl404};
|
||||
const int64_t fileSize = kBigFileSize;
|
||||
|
||||
std::unique_ptr<http::Request> const request{MakeFileRequest(urls, m_fileName, fileSize, 2048)};
|
||||
UNUSED_VALUE(request);
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFileDownloadSuccessAndClear();
|
||||
}
|
||||
|
||||
TEST_F(ChunksDownloaderTests, TwoThreadsWithInvalidSizeFail)
|
||||
{
|
||||
const std::vector<std::string> urls = {kTestUrlBigFile, kTestUrlBigFile};
|
||||
const int64_t fileSize = 12345;
|
||||
|
||||
std::unique_ptr<http::Request> const request{MakeFileRequest(urls, m_fileName, fileSize, 2048)};
|
||||
UNUSED_VALUE(request);
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFileDownloadFailAndClear();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
int64_t constexpr beg1 = 123, end1 = 1230, beg2 = 44000, end2 = 47683;
|
||||
|
||||
struct ResumeChecker
|
||||
{
|
||||
size_t m_counter;
|
||||
ResumeChecker() : m_counter(0) {}
|
||||
|
||||
void OnDownloadProgress(http::Request & request)
|
||||
{
|
||||
if (m_counter == 0)
|
||||
{
|
||||
EXPECT_EQ(request.GetProgress().m_bytesDownloaded, beg2);
|
||||
EXPECT_EQ(request.GetProgress().m_bytesTotal, kBigFileSize);
|
||||
}
|
||||
else if (m_counter == 1)
|
||||
{
|
||||
EXPECT_EQ(request.GetProgress().m_bytesDownloaded, kBigFileSize);
|
||||
EXPECT_EQ(request.GetProgress().m_bytesTotal, kBigFileSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_TRUE(false) << "Progress should be called exactly 2 times";
|
||||
}
|
||||
++m_counter;
|
||||
}
|
||||
|
||||
static void OnDownloadFinish(http::Request & request)
|
||||
{
|
||||
EXPECT_EQ(request.GetStatus(), DownloadStatus::Completed);
|
||||
QCoreApplication::exit();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(ChunksDownloaderTests, DownloadChunksWithResume)
|
||||
{
|
||||
std::vector<std::string> urls = {kTestUrlBigFile};
|
||||
|
||||
// 1st step - download full file
|
||||
{
|
||||
std::unique_ptr<http::Request> const request(MakeFileRequest(urls, m_fileName, kBigFileSize));
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectOK();
|
||||
uint64_t size;
|
||||
EXPECT_FALSE(base::GetFileSize(m_fileName + RESUME_FILE_EXTENSION, size)) << "No resume file on success";
|
||||
}
|
||||
|
||||
// 2nd step - mark some file blocks as not downloaded
|
||||
{
|
||||
// to substitute temporary not fully downloaded file
|
||||
EXPECT_TRUE(base::RenameFileX(m_fileName, m_fileName + DOWNLOADING_FILE_EXTENSION));
|
||||
|
||||
FileWriter f(m_fileName + DOWNLOADING_FILE_EXTENSION, FileWriter::OP_WRITE_EXISTING);
|
||||
f.Seek(beg1);
|
||||
char b1[end1 - beg1 + 1] = {0};
|
||||
f.Write(b1, ARRAY_SIZE(b1));
|
||||
|
||||
f.Seek(beg2);
|
||||
char b2[end2 - beg2 + 1] = {0};
|
||||
f.Write(b2, ARRAY_SIZE(b2));
|
||||
|
||||
ChunksDownloadStrategy strategy;
|
||||
strategy.AddChunk({int64_t(0), beg1 - 1}, ChunksDownloadStrategy::CHUNK_COMPLETE);
|
||||
strategy.AddChunk({beg1, end1}, ChunksDownloadStrategy::CHUNK_FREE);
|
||||
strategy.AddChunk({end1 + 1, beg2 - 1}, ChunksDownloadStrategy::CHUNK_COMPLETE);
|
||||
strategy.AddChunk({beg2, end2}, ChunksDownloadStrategy::CHUNK_FREE);
|
||||
|
||||
strategy.SaveChunks(kBigFileSize, m_fileName + RESUME_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
// 3rd step - check that resume works
|
||||
{
|
||||
ResumeChecker checker;
|
||||
std::unique_ptr<http::Request> const request(MakeFileRequest(urls, m_fileName, kBigFileSize, checker));
|
||||
QCoreApplication::exec();
|
||||
ExpectFileDownloadSuccessAndClear();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChunksDownloaderTests, DownloadChunksResumeAfterCancel)
|
||||
{
|
||||
std::vector<std::string> urls = {kTestUrlBigFile};
|
||||
int arrCancelChunks[] = {1, 3, 10, 15, 20, 0};
|
||||
|
||||
for (int arrCancelChunk : arrCancelChunks)
|
||||
{
|
||||
if (arrCancelChunk > 0)
|
||||
CancelDownloadOnGivenChunk(arrCancelChunk);
|
||||
|
||||
std::unique_ptr<http::Request> const request(MakeFileRequest(urls, m_fileName, kBigFileSize, 1024, false));
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
|
||||
ExpectFileDownloadSuccessAndClear();
|
||||
}
|
||||
} // namespace om::network::downloader::testing
|
117
network/network_tests/downloader/simple_downloader.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include "network/network_tests_support/request_fixture.hpp"
|
||||
|
||||
namespace om::network::downloader::testing
|
||||
{
|
||||
using namespace ::testing;
|
||||
using namespace om::network::testing;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CancelDownload
|
||||
{
|
||||
static void OnDownloadProgress(http::Request & request)
|
||||
{
|
||||
EXPECT_THAT(request.GetData(), Not(IsEmpty()));
|
||||
delete &request;
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
static void OnDownloadFinish(http::Request &) { ASSERT_TRUE(false) << "Should be never called"; }
|
||||
};
|
||||
|
||||
struct DeleteOnFinish
|
||||
{
|
||||
static void OnDownloadProgress(http::Request & request) { EXPECT_THAT(request.GetData(), Not(IsEmpty())); }
|
||||
|
||||
static void OnDownloadFinish(http::Request & request)
|
||||
{
|
||||
delete &request;
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class SimpleDownloaderTests : public RequestFixture
|
||||
{
|
||||
public:
|
||||
using RequestFixture::MakeGetRequest;
|
||||
|
||||
template <class Functor>
|
||||
auto MakeGetRequest(std::string const & url)
|
||||
{
|
||||
return http::Request::Get(
|
||||
url, [](http::Request & request) { Functor::OnDownloadFinish(request); },
|
||||
[](http::Request & request) { Functor::OnDownloadProgress(request); });
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SimpleDownloaderTests, SimpleHttpGetSuccess)
|
||||
{
|
||||
std::unique_ptr<http::Request> const request{MakeGetRequest(kTestUrl1)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectOK();
|
||||
EXPECT_EQ(request->GetData(), "Test1");
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, SimpleHttpsGetSuccess)
|
||||
{
|
||||
std::unique_ptr<http::Request> const request{MakeGetRequest("https://github.com")};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectOK();
|
||||
EXPECT_THAT(request->GetData(), Not(IsEmpty()));
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, FailOnRedirect)
|
||||
{
|
||||
// We DO NOT SUPPORT redirects to avoid data corruption when downloading mwm files
|
||||
std::unique_ptr<http::Request> const request{MakeGetRequest("http://localhost:34568/unit_tests/permanent")};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFail();
|
||||
EXPECT_THAT(request->GetData(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, FailWith404)
|
||||
{
|
||||
std::unique_ptr<http::Request> const request{MakeGetRequest(kTestUrl404)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFileNotFound();
|
||||
EXPECT_THAT(request->GetData(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, FailWhenNotExistingHost)
|
||||
{
|
||||
std::unique_ptr<http::Request> const request{MakeGetRequest("http://not-valid-host123532.ath.cx")};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectFail();
|
||||
EXPECT_THAT(request->GetData(), IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, CancelDownloadInTheMiddleOfTheProgress)
|
||||
{
|
||||
// should be deleted in canceler
|
||||
MakeGetRequest<CancelDownload>(kTestUrlBigFile);
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, DeleteRequestAtTheEndOfSuccessfulDownload)
|
||||
{
|
||||
// should be deleted in deleter on finish
|
||||
MakeGetRequest<DeleteOnFinish>(kTestUrl1);
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
|
||||
TEST_F(SimpleDownloaderTests, SimplePostRequest)
|
||||
{
|
||||
std::string const postData = R"({"jsonKey":"jsonValue"})";
|
||||
std::unique_ptr<http::Request> const request{MakePostRequest("http://localhost:34568/unit_tests/post.php", postData)};
|
||||
// wait until post is finished
|
||||
QCoreApplication::exec();
|
||||
ExpectOK();
|
||||
EXPECT_EQ(request->GetData(), postData);
|
||||
}
|
||||
} // namespace om::network::downloader::testing
|
90
network/network_tests/downloader/utils.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include "network/downloader/utils.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/mwm_version.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace om::network::downloader
|
||||
{
|
||||
TEST(DownloaderUtils, GetFilePathByUrl)
|
||||
{
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
std::string const fileName = platform::GetFileName(mwmName, MapFileType::Map);
|
||||
int64_t const dataVersion = version::FOR_TESTING_MWM1;
|
||||
int64_t const diffVersion = 0;
|
||||
MapFileType const fileType = MapFileType::Map;
|
||||
|
||||
auto const path = platform::GetFileDownloadPath(dataVersion, mwmName, fileType);
|
||||
|
||||
auto const url = GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
auto const resultPath = GetFilePathByUrl(url);
|
||||
|
||||
EXPECT_EQ(path, resultPath);
|
||||
}
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
std::string const fileName = platform::GetFileName(mwmName, MapFileType::Diff);
|
||||
int64_t const dataVersion = version::FOR_TESTING_MWM2;
|
||||
int64_t const diffVersion = version::FOR_TESTING_MWM1;
|
||||
MapFileType const fileType = MapFileType::Diff;
|
||||
|
||||
auto const path = platform::GetFileDownloadPath(dataVersion, mwmName, fileType);
|
||||
|
||||
auto const url = GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
auto const resultPath = GetFilePathByUrl(url);
|
||||
|
||||
EXPECT_EQ(path, resultPath);
|
||||
}
|
||||
|
||||
EXPECT_EQ(GetFilePathByUrl("/maps/220314/Belarus_Brest Region.mwm"),
|
||||
base::JoinPath(GetPlatform().WritableDir(), "220314/Belarus_Brest Region.mwm.ready"));
|
||||
}
|
||||
|
||||
TEST(DownloaderUtils, IsUrlSupported)
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
|
||||
std::string fileName = platform::GetFileName(mwmName, MapFileType::Map);
|
||||
int64_t dataVersion = version::FOR_TESTING_MWM1;
|
||||
int64_t diffVersion = 0;
|
||||
|
||||
auto url = GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
EXPECT_TRUE(IsUrlSupported(url));
|
||||
EXPECT_TRUE(IsUrlSupported("maps/991215/Luna.mwm"));
|
||||
EXPECT_TRUE(IsUrlSupported("maps/0/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("maps/x/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("macarena/0/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("/hack/maps/0/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("0/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("maps/0/Luna"));
|
||||
EXPECT_FALSE(IsUrlSupported("0/Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("Luna.mwm"));
|
||||
EXPECT_FALSE(IsUrlSupported("Luna"));
|
||||
|
||||
fileName = platform::GetFileName(mwmName, MapFileType::Diff);
|
||||
diffVersion = version::FOR_TESTING_MWM1;
|
||||
url = GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
EXPECT_TRUE(IsUrlSupported(url));
|
||||
EXPECT_TRUE(IsUrlSupported("diffs/991215/991215/Luna.mwmdiff"));
|
||||
EXPECT_TRUE(IsUrlSupported("diffs/0/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/x/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0/x/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/x/x/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("beefs/0/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0/0/Luna.mwmdiff.f"));
|
||||
EXPECT_FALSE(IsUrlSupported("maps/diffs/0/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0/0/Luna"));
|
||||
EXPECT_FALSE(IsUrlSupported("0/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/0/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("diffs/Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("Luna.mwmdiff"));
|
||||
EXPECT_FALSE(IsUrlSupported("Luna"));
|
||||
}
|
||||
} // namespace om::network::downloader
|
37
network/network_tests/servers_list.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "network/servers_list.hpp"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
TEST(ServersList, ParseMetaConfig)
|
||||
{
|
||||
std::optional<MetaConfig> cfg;
|
||||
|
||||
cfg = ParseMetaConfig(R"([ "https://url1/", "https://url2/" ])");
|
||||
EXPECT_TRUE(cfg.has_value());
|
||||
EXPECT_THAT(cfg->m_serversList, ::testing::ElementsAreArray({"https://url1/", "https://url2/"}));
|
||||
|
||||
cfg = ParseMetaConfig(R"(
|
||||
{
|
||||
"servers": [ "https://url1/", "https://url2/" ],
|
||||
"settings": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
}
|
||||
)");
|
||||
EXPECT_TRUE(cfg.has_value());
|
||||
EXPECT_THAT(cfg->m_serversList, ::testing::ElementsAreArray({"https://url1/", "https://url2/"}));
|
||||
EXPECT_EQ(cfg->m_settings.size(), 2);
|
||||
EXPECT_EQ(cfg->m_settings["key1"], "value1");
|
||||
EXPECT_EQ(cfg->m_settings["key2"], "value2");
|
||||
|
||||
EXPECT_FALSE(ParseMetaConfig(R"(broken json)"));
|
||||
EXPECT_FALSE(ParseMetaConfig(R"([])"));
|
||||
EXPECT_FALSE(ParseMetaConfig(R"({})"));
|
||||
EXPECT_FALSE(ParseMetaConfig(R"({"no_servers": "invalid"})"));
|
||||
EXPECT_FALSE(ParseMetaConfig(R"({"servers": "invalid"})"));
|
||||
}
|
||||
} // namespace om::network
|
17
network/network_tests_support/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
project(network_tests_support)
|
||||
|
||||
set(SRC
|
||||
request_fixture.hpp
|
||||
test_socket.cpp
|
||||
test_socket.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
network
|
||||
GTest::gtest_main
|
||||
gmock
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
)
|
115
network/network_tests_support/request_fixture.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "network/http/request.hpp"
|
||||
|
||||
namespace om::network::testing
|
||||
{
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Not;
|
||||
|
||||
char constexpr kTestUrl1[] = "http://localhost:34568/unit_tests/1.txt";
|
||||
char constexpr kTestUrl404[] = "http://localhost:34568/unit_tests/notexisting_unittest";
|
||||
char constexpr kTestUrlBigFile[] = "http://localhost:34568/unit_tests/47kb.file";
|
||||
|
||||
class RequestFixture : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
RequestFixture()
|
||||
: m_app{[]()
|
||||
{
|
||||
int args = 0;
|
||||
return QApplication(args, nullptr);
|
||||
}()}
|
||||
{
|
||||
}
|
||||
|
||||
auto MakeGetRequest(std::string const & url)
|
||||
{
|
||||
return http::Request::Get(
|
||||
url, [this](http::Request & request) { OnDownloadFinish(request); },
|
||||
[this](http::Request & request) { OnDownloadProgress(request); });
|
||||
}
|
||||
|
||||
auto MakePostRequest(std::string const & url, std::string const & data)
|
||||
{
|
||||
return http::Request::PostJson(
|
||||
url, data, [this](http::Request & request) { OnDownloadFinish(request); },
|
||||
[this](http::Request & request) { OnDownloadProgress(request); });
|
||||
}
|
||||
|
||||
auto MakeFileRequest(std::vector<std::string> const & urls, std::string const & fileName, int64_t fileSize,
|
||||
int64_t chunkSize = 512 * 1024, bool doCleanOnCancel = true)
|
||||
{
|
||||
return http::Request::GetFile(
|
||||
urls, fileName, fileSize, [this](http::Request & request) { OnDownloadFinish(request); },
|
||||
[this](http::Request & request) { OnDownloadProgress(request); }, chunkSize);
|
||||
}
|
||||
|
||||
void ExpectOK()
|
||||
{
|
||||
EXPECT_THAT(m_statuses, Not(IsEmpty())) << "Observer was not called";
|
||||
EXPECT_TRUE(m_progressWasCalled) << "Download progress wasn't called";
|
||||
for (auto const & status : m_statuses)
|
||||
EXPECT_EQ(status, DownloadStatus::Completed);
|
||||
}
|
||||
|
||||
void ExpectFail()
|
||||
{
|
||||
EXPECT_THAT(m_statuses, Not(IsEmpty())) << "Observer was not called";
|
||||
for (auto const & status : m_statuses)
|
||||
EXPECT_EQ(status, DownloadStatus::Failed);
|
||||
}
|
||||
|
||||
void ExpectFileNotFound()
|
||||
{
|
||||
EXPECT_THAT(m_statuses, Not(IsEmpty())) << "Observer was not called.";
|
||||
for (auto const & status : m_statuses)
|
||||
EXPECT_EQ(status, DownloadStatus::FileNotFound);
|
||||
}
|
||||
|
||||
protected:
|
||||
void CancelDownloadOnGivenChunk(int chunksToFail) { m_chunksToFail = chunksToFail; }
|
||||
|
||||
private:
|
||||
void OnDownloadProgress(http::Request & request)
|
||||
{
|
||||
m_progressWasCalled = true;
|
||||
EXPECT_EQ(request.GetStatus(), DownloadStatus::InProgress);
|
||||
|
||||
// Cancel download if needed
|
||||
if (m_chunksToFail != -1)
|
||||
{
|
||||
--m_chunksToFail;
|
||||
if (m_chunksToFail == 0)
|
||||
{
|
||||
m_chunksToFail = -1;
|
||||
LOG(LINFO, ("Download canceled"));
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDownloadFinish(http::Request & request)
|
||||
{
|
||||
m_statuses.emplace_back(request.GetStatus());
|
||||
EXPECT_NE(m_statuses.back(), DownloadStatus::InProgress);
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_progressWasCalled = false;
|
||||
/// Chunked downloads can return one status per chunk (thread)
|
||||
std::vector<DownloadStatus> m_statuses;
|
||||
/// Interrupt download after this number of chunks
|
||||
int m_chunksToFail = -1;
|
||||
base::ScopedLogLevelChanger const m_debugLogLevel{LDEBUG};
|
||||
QApplication m_app;
|
||||
};
|
||||
} // namespace om::network::testing
|
|
@ -8,10 +8,9 @@
|
|||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace platform
|
||||
{
|
||||
namespace tests_support
|
||||
namespace om::network::tests
|
||||
{
|
||||
|
||||
TestSocket::~TestSocket() { m_isConnected = false; }
|
||||
|
||||
bool TestSocket::Open(string const & host, uint16_t port)
|
||||
|
@ -58,13 +57,11 @@ void TestSocket::SetTimeout(uint32_t milliseconds) { m_timeoutMs = milliseconds;
|
|||
size_t TestSocket::ReadServer(vector<uint8_t> & destination)
|
||||
{
|
||||
unique_lock<mutex> lock(m_outputMutex);
|
||||
m_outputCondition.wait_for(lock, milliseconds(m_timeoutMs),
|
||||
[this]() { return !m_output.empty(); });
|
||||
m_outputCondition.wait_for(lock, milliseconds(m_timeoutMs), [this]() { return !m_output.empty(); });
|
||||
|
||||
size_t const outputSize = m_output.size();
|
||||
destination.insert(destination.end(), m_output.begin(), m_output.end());
|
||||
m_output.clear();
|
||||
return outputSize;
|
||||
}
|
||||
} // namespace tests_support
|
||||
} // namespace platform
|
||||
} // namespace om::network::tests
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
|
@ -11,9 +9,9 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace platform
|
||||
{
|
||||
namespace tests_support
|
||||
#include "network/socket.hpp"
|
||||
|
||||
namespace om::network::tests
|
||||
{
|
||||
class TestSocket final : public Socket
|
||||
{
|
||||
|
@ -50,5 +48,4 @@ private:
|
|||
std::mutex m_outputMutex;
|
||||
std::condition_variable m_outputCondition;
|
||||
};
|
||||
} // namespace tests_support
|
||||
} // namespace platform
|
||||
} // namespace om::network::tests
|
28
network/non_http_error_code.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace om::network::non_http_error_code
|
||||
{
|
||||
auto constexpr kIOException = -1;
|
||||
auto constexpr kWriteException = -2;
|
||||
auto constexpr kInconsistentFileSize = -3;
|
||||
auto constexpr kNonHttpResponse = -4;
|
||||
auto constexpr kInvalidURL = -5;
|
||||
auto constexpr kCancelled = -6;
|
||||
|
||||
inline std::string DebugPrint(long errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case kIOException: return "IO exception";
|
||||
case kWriteException: return "Write exception";
|
||||
case kInconsistentFileSize: return "Inconsistent file size";
|
||||
case kNonHttpResponse: return "Non-http response";
|
||||
case kInvalidURL: return "Invalid URL";
|
||||
case kCancelled: return "Cancelled";
|
||||
default: return std::to_string(errorCode);
|
||||
}
|
||||
}
|
||||
} // namespace om::network::non_http_error_code
|
28
network/progress.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
struct Progress
|
||||
{
|
||||
static int64_t constexpr kUnknownTotalSize = -1;
|
||||
|
||||
static Progress constexpr Unknown() { return {0, kUnknownTotalSize}; }
|
||||
|
||||
bool IsUnknown() const { return m_bytesTotal == kUnknownTotalSize; }
|
||||
|
||||
int64_t m_bytesDownloaded = 0;
|
||||
/// Total can be kUnknownTotalSize if size is unknown.
|
||||
int64_t m_bytesTotal = 0;
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(Progress const & progress)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "(downloaded " << progress.m_bytesDownloaded << " bytes out of " << progress.m_bytesTotal << " bytes)";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace om::network
|
|
@ -1,13 +1,14 @@
|
|||
#include "platform/remote_file.hpp"
|
||||
#include "remote_file.hpp"
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
namespace platform
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
@ -15,20 +16,20 @@ double constexpr kRequestTimeoutInSec = 5.0;
|
|||
} // namespace
|
||||
|
||||
RemoteFile::RemoteFile(std::string url, std::string accessToken /* = {} */,
|
||||
HttpClient::Headers const & defaultHeaders /* = {} */,
|
||||
bool allowRedirection /* = true */)
|
||||
http::Client::Headers const & defaultHeaders /* = {} */, bool allowRedirection /* = true */)
|
||||
: m_url(std::move(url))
|
||||
, m_accessToken(std::move(accessToken))
|
||||
, m_defaultHeaders(defaultHeaders)
|
||||
, m_allowRedirection(allowRedirection)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
RemoteFile::Result RemoteFile::Download(std::string const & filePath) const
|
||||
{
|
||||
if (m_url.empty())
|
||||
return {m_url, Status::NetworkError, "Empty URL"};
|
||||
|
||||
platform::HttpClient request(m_url);
|
||||
http::Client request(m_url);
|
||||
request.SetRawHeaders(m_defaultHeaders);
|
||||
request.SetTimeout(kRequestTimeoutInSec);
|
||||
request.SetFollowRedirects(m_allowRedirection);
|
||||
|
@ -71,21 +72,20 @@ RemoteFile::Result RemoteFile::Download(std::string const & filePath) const
|
|||
return {m_url, Status::NetworkError, "Unspecified network error"};
|
||||
}
|
||||
|
||||
void RemoteFile::DownloadAsync(std::string const & filePath,
|
||||
StartDownloadingHandler && startDownloadingHandler,
|
||||
void RemoteFile::DownloadAsync(std::string const & filePath, StartDownloadingHandler && startDownloadingHandler,
|
||||
ResultHandler && resultHandler) const
|
||||
{
|
||||
RemoteFile remoteFile = *this;
|
||||
GetPlatform().RunTask(Platform::Thread::Network,
|
||||
[filePath, remoteFile = std::move(remoteFile),
|
||||
startDownloadingHandler = std::move(startDownloadingHandler),
|
||||
resultHandler = std::move(resultHandler)]
|
||||
{
|
||||
if (startDownloadingHandler)
|
||||
startDownloadingHandler(filePath);
|
||||
auto result = remoteFile.Download(filePath);
|
||||
if (resultHandler)
|
||||
resultHandler(std::move(result), filePath);
|
||||
});
|
||||
GetPlatform().RunTask(
|
||||
Platform::Thread::Network,
|
||||
[filePath, remoteFile = std::move(remoteFile), startDownloadingHandler = std::move(startDownloadingHandler),
|
||||
resultHandler = std::move(resultHandler)]
|
||||
{
|
||||
if (startDownloadingHandler)
|
||||
startDownloadingHandler(filePath);
|
||||
auto result = remoteFile.Download(filePath);
|
||||
if (resultHandler)
|
||||
resultHandler(std::move(result), filePath);
|
||||
});
|
||||
}
|
||||
} // namespace platform
|
||||
} // namespace om::network
|
|
@ -1,12 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/http_client.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace platform
|
||||
#include "network/http/client.hpp"
|
||||
|
||||
namespace om::network
|
||||
{
|
||||
class RemoteFile
|
||||
{
|
||||
|
@ -28,32 +28,29 @@ public:
|
|||
std::string const m_description;
|
||||
|
||||
Result(std::string url, Status status, int httpCode, std::string description)
|
||||
: m_url(std::move(url))
|
||||
, m_status(status)
|
||||
, m_httpCode(httpCode)
|
||||
, m_description(std::move(description))
|
||||
{}
|
||||
: m_url(std::move(url)), m_status(status), m_httpCode(httpCode), m_description(std::move(description))
|
||||
{
|
||||
}
|
||||
Result(std::string url, Status status, std::string description)
|
||||
: Result(std::move(url), status, 0, std::move(description))
|
||||
{}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
explicit RemoteFile(std::string url, std::string accessToken = {},
|
||||
HttpClient::Headers const & defaultHeaders = {},
|
||||
explicit RemoteFile(std::string url, std::string accessToken = {}, http::Client::Headers const & defaultHeaders = {},
|
||||
bool allowRedirection = true);
|
||||
|
||||
Result Download(std::string const & filePath) const;
|
||||
|
||||
using StartDownloadingHandler = std::function<void(std::string const & filePath)>;
|
||||
using ResultHandler = std::function<void(Result &&, std::string const & filePath)>;
|
||||
void DownloadAsync(std::string const & filePath,
|
||||
StartDownloadingHandler && startDownloadingHandler,
|
||||
void DownloadAsync(std::string const & filePath, StartDownloadingHandler && startDownloadingHandler,
|
||||
ResultHandler && resultHandler) const;
|
||||
|
||||
private:
|
||||
std::string const m_url;
|
||||
std::string const m_accessToken;
|
||||
HttpClient::Headers const m_defaultHeaders;
|
||||
http::Client::Headers const m_defaultHeaders;
|
||||
bool const m_allowRedirection;
|
||||
};
|
||||
} // namespace platform
|
||||
} // namespace om::network
|
|
@ -1,14 +1,11 @@
|
|||
#include "platform/servers_list.hpp"
|
||||
#include "servers_list.hpp"
|
||||
|
||||
#include "platform/http_request.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "cppjansson/cppjansson.hpp"
|
||||
|
||||
namespace downloader
|
||||
namespace om::network
|
||||
{
|
||||
std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr)
|
||||
{
|
||||
|
@ -64,4 +61,4 @@ std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr)
|
|||
|
||||
return outMetaConfig;
|
||||
}
|
||||
} // namespace downloader
|
||||
} // namespace om::network
|
|
@ -5,7 +5,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace downloader
|
||||
namespace om::network
|
||||
{
|
||||
// Dynamic configuration from MetaServer.
|
||||
struct MetaConfig
|
||||
|
@ -17,4 +17,4 @@ struct MetaConfig
|
|||
};
|
||||
|
||||
std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr);
|
||||
} // namespace downloader
|
||||
} // namespace om::network
|
|
@ -4,7 +4,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace platform
|
||||
namespace om::network
|
||||
{
|
||||
class Socket
|
||||
{
|
||||
|
@ -26,16 +26,5 @@ public:
|
|||
virtual void SetTimeout(uint32_t milliseconds) = 0;
|
||||
};
|
||||
|
||||
class StubSocket final : public Socket
|
||||
{
|
||||
public:
|
||||
// Socket overrides:
|
||||
bool Open(std::string const &, uint16_t) override { return false; }
|
||||
void Close() override {}
|
||||
bool Read(uint8_t *, uint32_t) override { return false; }
|
||||
bool Write(uint8_t const *, uint32_t) override { return false; }
|
||||
void SetTimeout(uint32_t) override {}
|
||||
};
|
||||
|
||||
std::unique_ptr<Socket> CreateSocket();
|
||||
} // namespace platform
|
||||
} // namespace om::network
|
|
@ -4,8 +4,6 @@ set(SRC
|
|||
../private.h # To properly detect its changes with CMake.
|
||||
battery_tracker.cpp
|
||||
battery_tracker.hpp
|
||||
chunks_download_strategy.cpp
|
||||
chunks_download_strategy.hpp
|
||||
constants.hpp
|
||||
country_defines.cpp
|
||||
country_defines.hpp
|
||||
|
@ -15,21 +13,9 @@ set(SRC
|
|||
distance.hpp
|
||||
duration.cpp
|
||||
duration.hpp
|
||||
downloader_defines.hpp
|
||||
downloader_utils.cpp
|
||||
downloader_utils.hpp
|
||||
get_text_by_id.cpp
|
||||
get_text_by_id.hpp
|
||||
gui_thread.hpp
|
||||
http_client.cpp
|
||||
http_client.hpp
|
||||
http_payload.cpp
|
||||
http_payload.hpp
|
||||
http_request.cpp
|
||||
http_request.hpp
|
||||
http_thread_callback.hpp
|
||||
http_uploader.hpp
|
||||
http_uploader_background.hpp
|
||||
local_country_file.cpp
|
||||
local_country_file.hpp
|
||||
local_country_file_utils.cpp
|
||||
|
@ -48,14 +34,9 @@ set(SRC
|
|||
platform.hpp
|
||||
preferred_languages.cpp
|
||||
preferred_languages.hpp
|
||||
remote_file.cpp
|
||||
remote_file.hpp
|
||||
secure_storage.hpp
|
||||
servers_list.cpp
|
||||
servers_list.hpp
|
||||
settings.cpp
|
||||
settings.hpp
|
||||
socket.hpp
|
||||
trace.hpp
|
||||
string_storage_base.cpp
|
||||
string_storage_base.hpp
|
||||
|
@ -78,23 +59,13 @@ endif()
|
|||
|
||||
if (PLATFORM_IPHONE)
|
||||
append(SRC
|
||||
background_downloader_ios.h
|
||||
background_downloader_ios.mm
|
||||
gui_thread_apple.mm
|
||||
http_thread_apple.h
|
||||
http_thread_apple.mm
|
||||
http_client_apple.mm
|
||||
http_uploader_apple.mm
|
||||
http_user_agent_ios.mm
|
||||
localization.mm
|
||||
locale.mm
|
||||
network_policy_ios.h
|
||||
network_policy_ios.mm
|
||||
platform_ios.mm
|
||||
platform_unix_impl.cpp
|
||||
platform_unix_impl.hpp
|
||||
secure_storage_ios.mm
|
||||
socket_apple.mm
|
||||
)
|
||||
elseif(${PLATFORM_ANDROID})
|
||||
append(SRC
|
||||
|
@ -127,7 +98,6 @@ else() # neither iPhone nor Android
|
|||
localization_dummy.cpp
|
||||
location_service.cpp
|
||||
location_service.hpp
|
||||
network_policy_dummy.cpp
|
||||
platform_qt.cpp
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/platform_qt_version.cpp"
|
||||
)
|
||||
|
@ -135,11 +105,6 @@ else() # neither iPhone nor Android
|
|||
if (${PLATFORM_WIN})
|
||||
append(SRC
|
||||
gui_thread_qt.cpp
|
||||
http_client_curl.cpp
|
||||
http_thread_qt.cpp
|
||||
http_thread_qt.hpp
|
||||
http_uploader_background_dummy.cpp
|
||||
http_uploader_dummy.cpp
|
||||
locale_std.cpp
|
||||
platform_win.cpp
|
||||
secure_storage_dummy.cpp
|
||||
|
@ -148,27 +113,15 @@ else() # neither iPhone nor Android
|
|||
append(SRC
|
||||
apple_location_service.mm
|
||||
gui_thread_apple.mm
|
||||
http_client_apple.mm
|
||||
http_thread_apple.h
|
||||
http_thread_apple.mm
|
||||
http_uploader_apple.mm
|
||||
http_uploader_background_dummy.cpp
|
||||
locale.mm
|
||||
platform_mac.mm
|
||||
platform_unix_impl.cpp
|
||||
platform_unix_impl.hpp
|
||||
secure_storage_qt.cpp
|
||||
socket_apple.mm
|
||||
http_session_manager.mm
|
||||
)
|
||||
elseif(${PLATFORM_LINUX})
|
||||
append(SRC
|
||||
gui_thread_qt.cpp
|
||||
http_client_curl.cpp
|
||||
http_thread_qt.cpp
|
||||
http_thread_qt.hpp
|
||||
http_uploader_dummy.cpp
|
||||
http_uploader_background_dummy.cpp
|
||||
locale_std.cpp
|
||||
platform_linux.cpp
|
||||
platform_unix_impl.cpp
|
||||
|
@ -195,15 +148,11 @@ target_link_libraries(${PROJECT_NAME}
|
|||
geometry # mercator::YToLat
|
||||
coding
|
||||
$<$<BOOL:${PLATFORM_DESKTOP}>:Qt6::Core>
|
||||
$<$<BOOL:${PLATFORM_LINUX}>:Qt6::Network>
|
||||
$<$<BOOL:${PLATFORM_WIN}>:Qt6::Network>
|
||||
$<$<BOOL:${QT_POSITIONING}>:Qt6::Positioning>
|
||||
$<$<BOOL:${PLATFORM_MAC}>:
|
||||
-framework\ Foundation
|
||||
-framework\ SystemConfiguration
|
||||
-framework\ CoreLocation
|
||||
-framework\ CFNetwork
|
||||
-framework\ Security # SecPKCS12Import
|
||||
>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace downloader
|
||||
{
|
||||
enum class DownloadStatus
|
||||
{
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
FileNotFound,
|
||||
FailedSHA,
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(DownloadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case DownloadStatus::InProgress: return "In progress";
|
||||
case DownloadStatus::Completed: return "Completed";
|
||||
case DownloadStatus::Failed: return "Failed";
|
||||
case DownloadStatus::FileNotFound: return "File not found";
|
||||
case DownloadStatus::FailedSHA: return "Failed SHA check";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
struct Progress
|
||||
{
|
||||
static int64_t constexpr kUnknownTotalSize = -1;
|
||||
|
||||
static Progress constexpr Unknown()
|
||||
{
|
||||
return {0, kUnknownTotalSize};
|
||||
}
|
||||
|
||||
bool IsUnknown() const
|
||||
{
|
||||
return m_bytesTotal == kUnknownTotalSize;
|
||||
}
|
||||
|
||||
int64_t m_bytesDownloaded = 0;
|
||||
/// Total can be kUnknownTotalSize if size is unknown.
|
||||
int64_t m_bytesTotal = 0;
|
||||
};
|
||||
|
||||
inline std::string DebugPrint(Progress const & progress)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "(downloaded " << progress.m_bytesDownloaded << " bytes out of " << progress.m_bytesTotal
|
||||
<< " bytes)";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace downloader
|
|
@ -1,211 +0,0 @@
|
|||
#include "platform/http_client.hpp"
|
||||
|
||||
#include "coding/base64.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace platform
|
||||
{
|
||||
HttpClient::HttpClient(string const & url) : m_urlRequested(url)
|
||||
{
|
||||
}
|
||||
|
||||
bool HttpClient::RunHttpRequest(string & response, SuccessChecker checker /* = nullptr */)
|
||||
{
|
||||
static auto const simpleChecker = [](HttpClient const & request)
|
||||
{
|
||||
return request.ErrorCode() == 200;
|
||||
};
|
||||
|
||||
if (checker == nullptr)
|
||||
checker = simpleChecker;
|
||||
|
||||
if (RunHttpRequest() && checker(*this))
|
||||
{
|
||||
response = ServerResponse();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetUrlRequested(string const & url)
|
||||
{
|
||||
m_urlRequested = url;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetHttpMethod(string const & method)
|
||||
{
|
||||
m_httpMethod = method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetBodyFile(string const & body_file, string const & content_type,
|
||||
string const & http_method /* = "POST" */,
|
||||
string const & content_encoding /* = "" */)
|
||||
{
|
||||
m_inputFile = body_file;
|
||||
m_bodyData.clear();
|
||||
m_headers.emplace("Content-Type", content_type);
|
||||
m_httpMethod = http_method;
|
||||
m_headers.emplace("Content-Encoding", content_encoding);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetReceivedFile(string const & received_file)
|
||||
{
|
||||
m_outputFile = received_file;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetUserAndPassword(string const & user, string const & password)
|
||||
{
|
||||
m_headers.emplace("Authorization", "Basic " + base64::Encode(user + ":" + password));
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetCookies(string const & cookies)
|
||||
{
|
||||
m_cookies = cookies;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetFollowRedirects(bool followRedirects)
|
||||
{
|
||||
m_followRedirects = followRedirects;
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetRawHeader(string const & key, string const & value)
|
||||
{
|
||||
m_headers.emplace(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpClient & HttpClient::SetRawHeaders(Headers const & headers)
|
||||
{
|
||||
m_headers.insert(headers.begin(), headers.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void HttpClient::SetTimeout(double timeoutSec)
|
||||
{
|
||||
m_timeoutSec = timeoutSec;
|
||||
}
|
||||
|
||||
string const & HttpClient::UrlRequested() const
|
||||
{
|
||||
return m_urlRequested;
|
||||
}
|
||||
|
||||
string const & HttpClient::UrlReceived() const
|
||||
{
|
||||
return m_urlReceived;
|
||||
}
|
||||
|
||||
bool HttpClient::WasRedirected() const
|
||||
{
|
||||
return m_urlRequested != m_urlReceived;
|
||||
}
|
||||
|
||||
int HttpClient::ErrorCode() const
|
||||
{
|
||||
return m_errorCode;
|
||||
}
|
||||
|
||||
string const & HttpClient::ServerResponse() const
|
||||
{
|
||||
return m_serverResponse;
|
||||
}
|
||||
|
||||
string const & HttpClient::HttpMethod() const
|
||||
{
|
||||
return m_httpMethod;
|
||||
}
|
||||
|
||||
string HttpClient::CombinedCookies() const
|
||||
{
|
||||
string serverCookies;
|
||||
auto const it = m_headers.find("Set-Cookie");
|
||||
if (it != m_headers.end())
|
||||
serverCookies = it->second;
|
||||
|
||||
if (serverCookies.empty())
|
||||
return m_cookies;
|
||||
|
||||
if (m_cookies.empty())
|
||||
return serverCookies;
|
||||
|
||||
return serverCookies + "; " + m_cookies;
|
||||
}
|
||||
|
||||
string HttpClient::CookieByName(string name) const
|
||||
{
|
||||
string const str = CombinedCookies();
|
||||
name += "=";
|
||||
auto const cookie = str.find(name);
|
||||
auto const eq = cookie + name.size();
|
||||
if (cookie != string::npos && str.size() > eq)
|
||||
return str.substr(eq, str.find(';', eq) - eq);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HttpClient::LoadHeaders(bool loadHeaders)
|
||||
{
|
||||
m_loadHeaders = loadHeaders;
|
||||
}
|
||||
|
||||
HttpClient::Headers const & HttpClient::GetHeaders() const
|
||||
{
|
||||
return m_headers;
|
||||
}
|
||||
|
||||
// static
|
||||
string HttpClient::NormalizeServerCookies(string && cookies)
|
||||
{
|
||||
istringstream is(cookies);
|
||||
string str, result;
|
||||
|
||||
// Split by ", ". Can have invalid tokens here, expires= can also contain a comma.
|
||||
while (getline(is, str, ','))
|
||||
{
|
||||
size_t const leading = str.find_first_not_of(' ');
|
||||
if (leading != string::npos)
|
||||
str.substr(leading).swap(str);
|
||||
|
||||
// In the good case, we have '=' and it goes before any ' '.
|
||||
auto const eq = str.find('=');
|
||||
if (eq == string::npos)
|
||||
continue; // It's not a cookie: no valid key value pair.
|
||||
|
||||
auto const sp = str.find(' ');
|
||||
if (sp != string::npos && eq > sp)
|
||||
continue; // It's not a cookie: comma in expires date.
|
||||
|
||||
// Insert delimiter.
|
||||
if (!result.empty())
|
||||
result.append("; ");
|
||||
|
||||
// Read cookie itself.
|
||||
result.append(str, 0, str.find(';'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string DebugPrint(HttpClient const & request)
|
||||
{
|
||||
ostringstream ostr;
|
||||
ostr << "HTTP " << request.ErrorCode() << " url [" << request.UrlRequested() << "]";
|
||||
if (request.WasRedirected())
|
||||
ostr << " was redirected to [" << request.UrlReceived() << "]";
|
||||
if (!request.ServerResponse().empty())
|
||||
ostr << " response: " << request.ServerResponse();
|
||||
return ostr.str();
|
||||
}
|
||||
} // namespace platform
|
|
@ -1,6 +0,0 @@
|
|||
#include "platform/http_payload.hpp"
|
||||
|
||||
namespace platform
|
||||
{
|
||||
HttpPayload::HttpPayload() : m_method("POST"), m_fileKey("file"), m_needClientAuth(false) {}
|
||||
} // namespace platform
|
|
@ -1,402 +0,0 @@
|
|||
#include "platform/http_request.hpp"
|
||||
|
||||
#include "platform/chunks_download_strategy.hpp"
|
||||
#include "platform/http_thread_callback.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include "base/thread.hpp"
|
||||
#endif
|
||||
|
||||
#include "coding/internal/file_data.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
class HttpThread;
|
||||
|
||||
namespace downloader
|
||||
{
|
||||
namespace non_http_error_code
|
||||
{
|
||||
string DebugPrint(long errorCode)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case kIOException:
|
||||
return "IO exception";
|
||||
case kWriteException:
|
||||
return "Write exception";
|
||||
case kInconsistentFileSize:
|
||||
return "Inconsistent file size";
|
||||
case kNonHttpResponse:
|
||||
return "Non-http response";
|
||||
case kInvalidURL:
|
||||
return "Invalid URL";
|
||||
case kCancelled:
|
||||
return "Cancelled";
|
||||
default:
|
||||
return to_string(errorCode);
|
||||
}
|
||||
}
|
||||
} // namespace non_http_error_code
|
||||
|
||||
/// @return 0 if creation failed
|
||||
HttpThread * CreateNativeHttpThread(string const & url,
|
||||
IHttpThreadCallback & callback,
|
||||
int64_t begRange = 0,
|
||||
int64_t endRange = -1,
|
||||
int64_t expectedSize = -1,
|
||||
string const & postBody = string());
|
||||
void DeleteNativeHttpThread(HttpThread * thread);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Stores server response into the memory
|
||||
class MemoryHttpRequest : public HttpRequest, public IHttpThreadCallback
|
||||
{
|
||||
HttpThread * m_thread;
|
||||
|
||||
string m_requestUrl;
|
||||
string m_downloadedData;
|
||||
MemWriter<string> m_writer;
|
||||
|
||||
virtual bool OnWrite(int64_t, void const * buffer, size_t size)
|
||||
{
|
||||
m_writer.Write(buffer, size);
|
||||
m_progress.m_bytesDownloaded += size;
|
||||
if (m_onProgress)
|
||||
m_onProgress(*this);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void OnFinish(long httpOrErrorCode, int64_t, int64_t)
|
||||
{
|
||||
if (httpOrErrorCode == 200)
|
||||
{
|
||||
m_status = DownloadStatus::Completed;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const message = non_http_error_code::DebugPrint(httpOrErrorCode);
|
||||
LOG(LWARNING, ("HttpRequest error:", message));
|
||||
if (httpOrErrorCode == 404)
|
||||
m_status = DownloadStatus::FileNotFound;
|
||||
else
|
||||
m_status = DownloadStatus::Failed;
|
||||
}
|
||||
|
||||
m_onFinish(*this);
|
||||
}
|
||||
|
||||
public:
|
||||
MemoryHttpRequest(string const & url, Callback && onFinish, Callback && onProgress)
|
||||
: HttpRequest(std::move(onFinish), std::move(onProgress)),
|
||||
m_requestUrl(url), m_writer(m_downloadedData)
|
||||
{
|
||||
m_thread = CreateNativeHttpThread(url, *this);
|
||||
ASSERT ( m_thread, () );
|
||||
}
|
||||
|
||||
MemoryHttpRequest(string const & url, string const & postData,
|
||||
Callback && onFinish, Callback && onProgress)
|
||||
: HttpRequest(std::move(onFinish), std::move(onProgress)), m_writer(m_downloadedData)
|
||||
{
|
||||
m_thread = CreateNativeHttpThread(url, *this, 0, -1, -1, postData);
|
||||
ASSERT ( m_thread, () );
|
||||
}
|
||||
|
||||
virtual ~MemoryHttpRequest()
|
||||
{
|
||||
DeleteNativeHttpThread(m_thread);
|
||||
}
|
||||
|
||||
virtual string const & GetData() const
|
||||
{
|
||||
return m_downloadedData;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class FileHttpRequest : public HttpRequest, public IHttpThreadCallback
|
||||
{
|
||||
ChunksDownloadStrategy m_strategy;
|
||||
typedef pair<HttpThread *, int64_t> ThreadHandleT;
|
||||
typedef list<ThreadHandleT> ThreadsContainerT;
|
||||
ThreadsContainerT m_threads;
|
||||
|
||||
string m_filePath;
|
||||
unique_ptr<FileWriter> m_writer;
|
||||
|
||||
size_t m_goodChunksCount;
|
||||
bool m_doCleanProgressFiles;
|
||||
|
||||
ChunksDownloadStrategy::ResultT StartThreads()
|
||||
{
|
||||
string url;
|
||||
pair<int64_t, int64_t> range;
|
||||
ChunksDownloadStrategy::ResultT result;
|
||||
while ((result = m_strategy.NextChunk(url, range)) == ChunksDownloadStrategy::ENextChunk)
|
||||
{
|
||||
HttpThread * p = CreateNativeHttpThread(url, *this, range.first, range.second, m_progress.m_bytesTotal);
|
||||
ASSERT ( p, () );
|
||||
m_threads.push_back(make_pair(p, range.first));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class ThreadByPos
|
||||
{
|
||||
int64_t m_pos;
|
||||
public:
|
||||
explicit ThreadByPos(int64_t pos) : m_pos(pos) {}
|
||||
inline bool operator() (ThreadHandleT const & p) const
|
||||
{
|
||||
return (p.second == m_pos);
|
||||
}
|
||||
};
|
||||
|
||||
void RemoveHttpThreadByKey(int64_t begRange)
|
||||
{
|
||||
ThreadsContainerT::iterator it = find_if(m_threads.begin(), m_threads.end(),
|
||||
ThreadByPos(begRange));
|
||||
if (it != m_threads.end())
|
||||
{
|
||||
HttpThread * p = it->first;
|
||||
m_threads.erase(it);
|
||||
DeleteNativeHttpThread(p);
|
||||
}
|
||||
else
|
||||
LOG(LERROR, ("Tried to remove invalid thread for position", begRange));
|
||||
}
|
||||
|
||||
virtual bool OnWrite(int64_t offset, void const * buffer, size_t size)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
static threads::ThreadID const id = threads::GetCurrentThreadID();
|
||||
ASSERT_EQUAL(id, threads::GetCurrentThreadID(), ("OnWrite called from different threads"));
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
m_writer->Seek(offset);
|
||||
m_writer->Write(buffer, size);
|
||||
return true;
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't write buffer for size", size, e.Msg()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveResumeChunks()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Flush writer before saving downloaded chunks.
|
||||
m_writer->Flush();
|
||||
|
||||
m_strategy.SaveChunks(m_progress.m_bytesTotal, m_filePath + RESUME_FILE_EXTENSION);
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't flush writer", e.Msg()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Called for each chunk by one main (GUI) thread.
|
||||
virtual void OnFinish(long httpOrErrorCode, int64_t begRange, int64_t endRange)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
static threads::ThreadID const id = threads::GetCurrentThreadID();
|
||||
ASSERT_EQUAL(id, threads::GetCurrentThreadID(), ("OnFinish called from different threads"));
|
||||
#endif
|
||||
|
||||
bool const isChunkOk = (httpOrErrorCode == 200);
|
||||
string const urlError = m_strategy.ChunkFinished(isChunkOk, make_pair(begRange, endRange));
|
||||
|
||||
// remove completed chunk from the list, beg is the key
|
||||
RemoveHttpThreadByKey(begRange);
|
||||
|
||||
// report progress
|
||||
if (isChunkOk)
|
||||
{
|
||||
m_progress.m_bytesDownloaded += (endRange - begRange) + 1;
|
||||
if (m_onProgress)
|
||||
m_onProgress(*this);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const message = non_http_error_code::DebugPrint(httpOrErrorCode);
|
||||
LOG(LWARNING, (m_filePath, "HttpRequest error:", message));
|
||||
}
|
||||
|
||||
ChunksDownloadStrategy::ResultT const result = StartThreads();
|
||||
if (result == ChunksDownloadStrategy::EDownloadFailed)
|
||||
m_status = httpOrErrorCode == 404 ? DownloadStatus::FileNotFound : DownloadStatus::Failed;
|
||||
else if (result == ChunksDownloadStrategy::EDownloadSucceeded)
|
||||
m_status = DownloadStatus::Completed;
|
||||
|
||||
if (isChunkOk)
|
||||
{
|
||||
// save information for download resume
|
||||
++m_goodChunksCount;
|
||||
if (m_status != DownloadStatus::Completed && m_goodChunksCount % 10 == 0)
|
||||
SaveResumeChunks();
|
||||
}
|
||||
|
||||
if (m_status == DownloadStatus::InProgress)
|
||||
return;
|
||||
|
||||
// 1. Save downloaded chunks if some error occured.
|
||||
if (m_status == DownloadStatus::Failed || m_status == DownloadStatus::FileNotFound)
|
||||
SaveResumeChunks();
|
||||
|
||||
// 2. Free file handle.
|
||||
CloseWriter();
|
||||
|
||||
// 3. Clean up resume file with chunks range on success
|
||||
if (m_status == DownloadStatus::Completed)
|
||||
{
|
||||
Platform::RemoveFileIfExists(m_filePath + RESUME_FILE_EXTENSION);
|
||||
|
||||
// Rename finished file to it's original name.
|
||||
Platform::RemoveFileIfExists(m_filePath);
|
||||
base::RenameFileX(m_filePath + DOWNLOADING_FILE_EXTENSION, m_filePath);
|
||||
}
|
||||
|
||||
// 4. Finish downloading.
|
||||
m_onFinish(*this);
|
||||
}
|
||||
|
||||
void CloseWriter()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_writer.reset();
|
||||
}
|
||||
catch (Writer::Exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Can't close file correctly", e.Msg()));
|
||||
|
||||
m_status = DownloadStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
FileHttpRequest(vector<string> const & urls, string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish, Callback && onProgress,
|
||||
int64_t chunkSize, bool doCleanProgressFiles)
|
||||
: HttpRequest(std::move(onFinish), std::move(onProgress)),
|
||||
m_strategy(urls), m_filePath(filePath),
|
||||
m_goodChunksCount(0), m_doCleanProgressFiles(doCleanProgressFiles)
|
||||
{
|
||||
ASSERT ( !urls.empty(), () );
|
||||
|
||||
// Load resume downloading information.
|
||||
m_progress.m_bytesDownloaded = m_strategy.LoadOrInitChunks(m_filePath + RESUME_FILE_EXTENSION,
|
||||
fileSize, chunkSize);
|
||||
m_progress.m_bytesTotal = fileSize;
|
||||
|
||||
FileWriter::Op openMode = FileWriter::OP_WRITE_TRUNCATE;
|
||||
if (m_progress.m_bytesDownloaded != 0)
|
||||
{
|
||||
// Check that resume information is correct with existing file.
|
||||
uint64_t size;
|
||||
if (base::GetFileSize(filePath + DOWNLOADING_FILE_EXTENSION, size) &&
|
||||
size <= static_cast<uint64_t>(fileSize))
|
||||
openMode = FileWriter::OP_WRITE_EXISTING;
|
||||
else
|
||||
m_strategy.InitChunks(fileSize, chunkSize);
|
||||
}
|
||||
|
||||
// Create file and reserve needed size.
|
||||
unique_ptr<FileWriter> writer(new FileWriter(filePath + DOWNLOADING_FILE_EXTENSION, openMode));
|
||||
|
||||
// Assign here, because previous functions can throw an exception.
|
||||
m_writer.swap(writer);
|
||||
Platform::DisableBackupForFile(filePath + DOWNLOADING_FILE_EXTENSION);
|
||||
StartThreads();
|
||||
}
|
||||
|
||||
virtual ~FileHttpRequest()
|
||||
{
|
||||
// Do safe delete with removing from list in case if DeleteNativeHttpThread
|
||||
// can produce final notifications to this->OnFinish().
|
||||
while (!m_threads.empty())
|
||||
{
|
||||
HttpThread * p = m_threads.back().first;
|
||||
m_threads.pop_back();
|
||||
DeleteNativeHttpThread(p);
|
||||
}
|
||||
|
||||
if (m_status == DownloadStatus::InProgress)
|
||||
{
|
||||
// means that client canceled download process, so delete all temporary files
|
||||
CloseWriter();
|
||||
|
||||
if (m_doCleanProgressFiles)
|
||||
{
|
||||
Platform::RemoveFileIfExists(m_filePath + DOWNLOADING_FILE_EXTENSION);
|
||||
Platform::RemoveFileIfExists(m_filePath + RESUME_FILE_EXTENSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual string const & GetData() const
|
||||
{
|
||||
return m_filePath;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
HttpRequest::HttpRequest(Callback && onFinish, Callback && onProgress)
|
||||
: m_status(DownloadStatus::InProgress)
|
||||
, m_progress(Progress::Unknown())
|
||||
, m_onFinish(std::move(onFinish))
|
||||
, m_onProgress(std::move(onProgress))
|
||||
{
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
}
|
||||
|
||||
HttpRequest * HttpRequest::Get(string const & url, Callback && onFinish, Callback && onProgress)
|
||||
{
|
||||
return new MemoryHttpRequest(url, std::move(onFinish), std::move(onProgress));
|
||||
}
|
||||
|
||||
HttpRequest * HttpRequest::PostJson(string const & url, string const & postData,
|
||||
Callback && onFinish, Callback && onProgress)
|
||||
{
|
||||
return new MemoryHttpRequest(url, postData, std::move(onFinish), std::move(onProgress));
|
||||
}
|
||||
|
||||
HttpRequest * HttpRequest::GetFile(vector<string> const & urls,
|
||||
string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish, Callback && onProgress,
|
||||
int64_t chunkSize, bool doCleanOnCancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileHttpRequest(urls, filePath, fileSize, std::move(onFinish), std::move(onProgress),
|
||||
chunkSize, doCleanOnCancel);
|
||||
}
|
||||
catch (FileWriter::Exception const & e)
|
||||
{
|
||||
// Can't create or open file for writing.
|
||||
LOG(LWARNING, ("Can't create file", filePath, "with size", fileSize, e.Msg()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace downloader
|
|
@ -1,64 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/downloader_defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace downloader
|
||||
{
|
||||
namespace non_http_error_code
|
||||
{
|
||||
auto constexpr kIOException = -1;
|
||||
auto constexpr kWriteException = -2;
|
||||
auto constexpr kInconsistentFileSize = -3;
|
||||
auto constexpr kNonHttpResponse = -4;
|
||||
auto constexpr kInvalidURL = -5;
|
||||
auto constexpr kCancelled = -6;
|
||||
} // namespace non_http_error_code
|
||||
|
||||
/// Request in progress will be canceled on delete
|
||||
class HttpRequest
|
||||
{
|
||||
public:
|
||||
using Callback = std::function<void(HttpRequest & request)>;
|
||||
|
||||
protected:
|
||||
DownloadStatus m_status;
|
||||
Progress m_progress;
|
||||
Callback m_onFinish;
|
||||
Callback m_onProgress;
|
||||
|
||||
HttpRequest(Callback && onFinish, Callback && onProgress);
|
||||
|
||||
public:
|
||||
virtual ~HttpRequest() = 0;
|
||||
|
||||
DownloadStatus GetStatus() const { return m_status; }
|
||||
Progress const & GetProgress() const { return m_progress; }
|
||||
/// Either file path (for chunks) or downloaded data
|
||||
virtual std::string const & GetData() const = 0;
|
||||
|
||||
/// Response saved to memory buffer and retrieved with Data()
|
||||
static HttpRequest * Get(std::string const & url,
|
||||
Callback && onFinish,
|
||||
Callback && onProgress = Callback());
|
||||
|
||||
/// Content-type for request is always "application/json"
|
||||
static HttpRequest * PostJson(std::string const & url, std::string const & postData,
|
||||
Callback && onFinish,
|
||||
Callback && onProgress = Callback());
|
||||
|
||||
/// Download file to filePath.
|
||||
/// @param[in] fileSize Correct file size (needed for resuming and reserving).
|
||||
static HttpRequest * GetFile(std::vector<std::string> const & urls,
|
||||
std::string const & filePath, int64_t fileSize,
|
||||
Callback && onFinish,
|
||||
Callback && onProgress = Callback(),
|
||||
int64_t chunkSize = 512 * 1024,
|
||||
bool doCleanOnCancel = true);
|
||||
};
|
||||
} // namespace downloader
|
|
@ -1,36 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace downloader { class IHttpThreadCallback; }
|
||||
|
||||
class HttpThread : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
HttpThread(std::string const & url,
|
||||
downloader::IHttpThreadCallback & cb,
|
||||
int64_t beg,
|
||||
int64_t end,
|
||||
int64_t size,
|
||||
std::string const & pb);
|
||||
virtual ~HttpThread();
|
||||
|
||||
private slots:
|
||||
void OnHeadersReceived();
|
||||
void OnChunkDownloaded();
|
||||
void OnDownloadFinished();
|
||||
|
||||
private:
|
||||
downloader::IHttpThreadCallback & m_callback;
|
||||
QNetworkReply * m_reply;
|
||||
int64_t m_begRange;
|
||||
int64_t m_endRange;
|
||||
int64_t m_downloadedBytes;
|
||||
int64_t m_expectedSize;
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/http_payload.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace platform
|
||||
{
|
||||
class HttpUploader
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
int32_t m_httpCode = 0;
|
||||
std::string m_description;
|
||||
};
|
||||
|
||||
HttpUploader() = delete;
|
||||
explicit HttpUploader(HttpPayload const & payload) : m_payload(payload) {}
|
||||
HttpPayload const & GetPayload() const { return m_payload; }
|
||||
Result Upload() const;
|
||||
|
||||
private:
|
||||
HttpPayload const m_payload;
|
||||
};
|
||||
} // namespace platform
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/http_payload.hpp"
|
||||
|
||||
namespace platform
|
||||
{
|
||||
class HttpUploaderBackground
|
||||
{
|
||||
public:
|
||||
HttpUploaderBackground() = delete;
|
||||
explicit HttpUploaderBackground(HttpPayload const & payload) : m_payload(payload) {}
|
||||
HttpPayload const & GetPayload() const { return m_payload; }
|
||||
|
||||
// TODO add platform-specific implementation
|
||||
void Upload() const;
|
||||
|
||||
private:
|
||||
HttpPayload m_payload;
|
||||
};
|
||||
} // namespace platform
|
|
@ -1,94 +0,0 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "http_uploader_background.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
static NSString *const kSessionId = @"MWMBackgroundUploader_sessionId";
|
||||
|
||||
@interface MWMBackgroundUploader : NSObject <NSURLSessionDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSURLSession *session;
|
||||
|
||||
+ (MWMBackgroundUploader *)sharedUploader;
|
||||
- (void)upload:(platform::HttpPayload const &)payload;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MWMBackgroundUploader
|
||||
|
||||
+ (MWMBackgroundUploader *)sharedUploader {
|
||||
static MWMBackgroundUploader *uploader;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
uploader = [[MWMBackgroundUploader alloc] init];
|
||||
});
|
||||
return uploader;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSURLSessionConfiguration *config =
|
||||
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSessionId];
|
||||
config.allowsCellularAccess = NO;
|
||||
config.sessionSendsLaunchEvents = NO;
|
||||
config.discretionary = YES;
|
||||
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)upload:(platform::HttpPayload const &)payload {
|
||||
NSURLComponents *components = [[NSURLComponents alloc] initWithString:@(payload.m_url.c_str())];
|
||||
NSMutableArray *newQueryItems = [NSMutableArray arrayWithArray:components.queryItems];
|
||||
std::for_each(payload.m_params.begin(), payload.m_params.end(), [newQueryItems](auto const &pair) {
|
||||
[newQueryItems addObject:[NSURLQueryItem queryItemWithName:@(pair.first.c_str()) value:@(pair.second.c_str())]];
|
||||
});
|
||||
[components setQueryItems:newQueryItems];
|
||||
NSURL *url = components.URL;
|
||||
CHECK(url, ());
|
||||
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
request.HTTPMethod = @(payload.m_method.c_str());
|
||||
std::for_each(payload.m_headers.begin(), payload.m_headers.end(), [request](auto const &pair) {
|
||||
[request addValue:@(pair.second.c_str()) forHTTPHeaderField:@(pair.first.c_str())];
|
||||
});
|
||||
|
||||
NSURL *fileUrl = [NSURL fileURLWithPath:@(payload.m_filePath.c_str())];
|
||||
CHECK(fileUrl, ());
|
||||
|
||||
[[self.session uploadTaskWithRequest:request fromFile:fileUrl] resume];
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] removeItemAtURL:fileUrl error:&error];
|
||||
if (error) {
|
||||
LOG(LDEBUG, ([error description].UTF8String));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(nullable NSError *)error {
|
||||
if (error) {
|
||||
LOG(LDEBUG, ("Upload failed:", [error description].UTF8String));
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential *_Nullable credential))completionHandler {
|
||||
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
namespace platform {
|
||||
void HttpUploaderBackground::Upload() const {
|
||||
[[MWMBackgroundUploader sharedUploader] upload:GetPayload()];
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
#include "platform/http_uploader_background.hpp"
|
||||
|
||||
namespace platform
|
||||
{
|
||||
void HttpUploaderBackground::Upload() const {}
|
||||
} // namespace platform
|
|
@ -1,12 +0,0 @@
|
|||
#include "platform/http_uploader.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace platform
|
||||
{
|
||||
HttpUploader::Result HttpUploader::Upload() const
|
||||
{
|
||||
// Dummy implementation.
|
||||
return {};
|
||||
}
|
||||
} // namespace platform
|
|
@ -1,30 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct _JNIEnv;
|
||||
class _jobject;
|
||||
typedef _JNIEnv JNIEnv;
|
||||
typedef _jobject * jobject;
|
||||
|
||||
namespace platform
|
||||
{
|
||||
/// Class that is used to allow or disallow remote calls.
|
||||
class NetworkPolicy
|
||||
{
|
||||
// Maker for android.
|
||||
friend NetworkPolicy ToNativeNetworkPolicy(JNIEnv * env, jobject obj);
|
||||
|
||||
friend NetworkPolicy GetCurrentNetworkPolicy();
|
||||
|
||||
public:
|
||||
bool CanUse() const { return m_canUse; }
|
||||
|
||||
private:
|
||||
NetworkPolicy(bool const canUseNetwork) : m_canUse(canUseNetwork) {}
|
||||
|
||||
bool m_canUse = false;
|
||||
};
|
||||
|
||||
extern NetworkPolicy GetCurrentNetworkPolicy();
|
||||
} // namespace platform
|
|
@ -1,9 +0,0 @@
|
|||
#include "platform/network_policy.hpp"
|
||||
|
||||
namespace platform
|
||||
{
|
||||
NetworkPolicy GetCurrentNetworkPolicy()
|
||||
{
|
||||
return NetworkPolicy(true);
|
||||
}
|
||||
} // namespace platform
|
|
@ -1,8 +1,6 @@
|
|||
#include "private.h"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
|
@ -65,15 +63,6 @@ bool IsDirWritable(std::string const & dir)
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace platform
|
||||
{
|
||||
std::unique_ptr<Socket> CreateSocket()
|
||||
{
|
||||
return std::unique_ptr<Socket>();
|
||||
}
|
||||
} // namespace platform
|
||||
|
||||
|
||||
Platform::Platform()
|
||||
{
|
||||
using base::JoinPath;
|
||||
|
|
|
@ -5,8 +5,6 @@ set(SRC
|
|||
country_file_tests.cpp
|
||||
distance_tests.cpp
|
||||
duration_tests.cpp
|
||||
downloader_tests/downloader_test.cpp
|
||||
downloader_utils_tests.cpp
|
||||
get_text_by_id_tests.cpp
|
||||
jansson_test.cpp
|
||||
language_test.cpp
|
||||
|
@ -17,7 +15,7 @@ set(SRC
|
|||
utm_mgrs_utils_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT REQUIRE_SERVER)
|
||||
omim_add_test(${PROJECT_NAME} ${SRC} REQUIRE_QT)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
platform_tests_support
|
||||
|
|
|
@ -1,597 +0,0 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "platform/http_request.hpp"
|
||||
#include "platform/chunks_download_strategy.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
#include "coding/internal/file_data.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/std_serialization.hpp"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace downloader_test
|
||||
{
|
||||
using namespace downloader;
|
||||
using namespace std::placeholders;
|
||||
using namespace std;
|
||||
|
||||
char constexpr kTestUrl1[] = "http://localhost:34568/unit_tests/1.txt";
|
||||
char constexpr kTestUrl404[] = "http://localhost:34568/unit_tests/notexisting_unittest";
|
||||
char constexpr kTestUrlBigFile[] = "http://localhost:34568/unit_tests/47kb.file";
|
||||
|
||||
// Should match file size in tools/python/ResponseProvider.py
|
||||
int constexpr kBigFileSize = 47684;
|
||||
|
||||
class DownloadObserver
|
||||
{
|
||||
bool m_progressWasCalled;
|
||||
// Chunked downloads can return one status per chunk (thread).
|
||||
vector<DownloadStatus> m_statuses;
|
||||
// Interrupt download after this number of chunks
|
||||
int m_chunksToFail;
|
||||
base::ScopedLogLevelChanger const m_debugLogLevel;
|
||||
|
||||
public:
|
||||
DownloadObserver() : m_chunksToFail(-1), m_debugLogLevel(LDEBUG)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void CancelDownloadOnGivenChunk(int chunksToFail)
|
||||
{
|
||||
m_chunksToFail = chunksToFail;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_progressWasCalled = false;
|
||||
m_statuses.clear();
|
||||
}
|
||||
|
||||
void TestOk()
|
||||
{
|
||||
TEST_NOT_EQUAL(0, m_statuses.size(), ("Observer was not called."));
|
||||
TEST(m_progressWasCalled, ("Download progress wasn't called"));
|
||||
for (auto const & status : m_statuses)
|
||||
TEST_EQUAL(status, DownloadStatus::Completed, ());
|
||||
}
|
||||
|
||||
void TestFailed()
|
||||
{
|
||||
TEST_NOT_EQUAL(0, m_statuses.size(), ("Observer was not called."));
|
||||
for (auto const & status : m_statuses)
|
||||
TEST_EQUAL(status, DownloadStatus::Failed, ());
|
||||
}
|
||||
|
||||
void TestFileNotFound()
|
||||
{
|
||||
TEST_NOT_EQUAL(0, m_statuses.size(), ("Observer was not called."));
|
||||
for (auto const & status : m_statuses)
|
||||
TEST_EQUAL(status, DownloadStatus::FileNotFound, ());
|
||||
}
|
||||
|
||||
void OnDownloadProgress(HttpRequest & request)
|
||||
{
|
||||
m_progressWasCalled = true;
|
||||
TEST_EQUAL(request.GetStatus(), DownloadStatus::InProgress, ());
|
||||
|
||||
// Cancel download if needed
|
||||
if (m_chunksToFail != -1)
|
||||
{
|
||||
--m_chunksToFail;
|
||||
if (m_chunksToFail == 0)
|
||||
{
|
||||
m_chunksToFail = -1;
|
||||
LOG(LINFO, ("Download canceled"));
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void OnDownloadFinish(HttpRequest & request)
|
||||
{
|
||||
auto const status = request.GetStatus();
|
||||
m_statuses.emplace_back(status);
|
||||
TEST(status != DownloadStatus::InProgress, ());
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
};
|
||||
|
||||
struct CancelDownload
|
||||
{
|
||||
void OnProgress(HttpRequest & request)
|
||||
{
|
||||
TEST_GREATER(request.GetData().size(), 0, ());
|
||||
delete &request;
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
void OnFinish(HttpRequest &)
|
||||
{
|
||||
TEST(false, ("Should be never called"));
|
||||
}
|
||||
};
|
||||
|
||||
struct DeleteOnFinish
|
||||
{
|
||||
void OnProgress(HttpRequest & request)
|
||||
{
|
||||
TEST_GREATER(request.GetData().size(), 0, ());
|
||||
}
|
||||
void OnFinish(HttpRequest & request)
|
||||
{
|
||||
delete &request;
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
};
|
||||
|
||||
UNIT_TEST(DownloaderSimpleGet)
|
||||
{
|
||||
DownloadObserver observer;
|
||||
auto const MakeRequest = [&observer](std::string const & url)
|
||||
{
|
||||
return HttpRequest::Get(url,
|
||||
bind(&DownloadObserver::OnDownloadFinish, &observer, _1),
|
||||
bind(&DownloadObserver::OnDownloadProgress, &observer, _1));
|
||||
};
|
||||
|
||||
{
|
||||
// simple success case
|
||||
unique_ptr<HttpRequest> const request {MakeRequest(kTestUrl1)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
TEST_EQUAL(request->GetData(), "Test1", ());
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
{
|
||||
// We DO NOT SUPPORT redirects to avoid data corruption when downloading mwm files
|
||||
unique_ptr<HttpRequest> const request {MakeRequest("http://localhost:34568/unit_tests/permanent")};
|
||||
QCoreApplication::exec();
|
||||
observer.TestFailed();
|
||||
TEST_EQUAL(request->GetData().size(), 0, (request->GetData()));
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
{
|
||||
// fail case 404
|
||||
unique_ptr<HttpRequest> const request {MakeRequest(kTestUrl404)};
|
||||
QCoreApplication::exec();
|
||||
observer.TestFileNotFound();
|
||||
TEST_EQUAL(request->GetData().size(), 0, (request->GetData()));
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
{
|
||||
// fail case not existing host
|
||||
unique_ptr<HttpRequest> const request {MakeRequest("http://not-valid-host123532.ath.cx")};
|
||||
QCoreApplication::exec();
|
||||
observer.TestFailed();
|
||||
TEST_EQUAL(request->GetData().size(), 0, (request->GetData()));
|
||||
}
|
||||
|
||||
{
|
||||
// cancel download in the middle of the progress
|
||||
CancelDownload canceler;
|
||||
// should be deleted in canceler
|
||||
HttpRequest::Get(kTestUrlBigFile,
|
||||
bind(&CancelDownload::OnFinish, &canceler, _1),
|
||||
bind(&CancelDownload::OnProgress, &canceler, _1));
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
{
|
||||
// https success case
|
||||
unique_ptr<HttpRequest> const request {MakeRequest("https://github.com")};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
TEST_GREATER(request->GetData().size(), 0, ());
|
||||
}
|
||||
|
||||
{
|
||||
// Delete request at the end of successful download
|
||||
DeleteOnFinish deleter;
|
||||
// should be deleted in deleter on finish
|
||||
HttpRequest::Get(kTestUrl1,
|
||||
bind(&DeleteOnFinish::OnFinish, &deleter, _1),
|
||||
bind(&DeleteOnFinish::OnProgress, &deleter, _1));
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This test sometimes fails on CI. Reasons are unknown.
|
||||
#ifndef OMIM_OS_MAC
|
||||
UNIT_TEST(DownloaderSimplePost)
|
||||
{
|
||||
// simple success case
|
||||
string const postData = "{\"jsonKey\":\"jsonValue\"}";
|
||||
DownloadObserver observer;
|
||||
unique_ptr<HttpRequest> const request {HttpRequest::PostJson("http://localhost:34568/unit_tests/post.php", postData,
|
||||
bind(&DownloadObserver::OnDownloadFinish, &observer, _1),
|
||||
bind(&DownloadObserver::OnDownloadProgress, &observer, _1))};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
TEST_EQUAL(request->GetData(), postData, ());
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIT_TEST(ChunksDownloadStrategy)
|
||||
{
|
||||
vector<string> const servers = {"UrlOfServer1", "UrlOfServer2", "UrlOfServer3"};
|
||||
|
||||
typedef pair<int64_t, int64_t> RangeT;
|
||||
RangeT const R1{0, 249}, R2{250, 499}, R3{500, 749}, R4{750, 800};
|
||||
|
||||
int64_t constexpr kFileSize = 800;
|
||||
int64_t constexpr kChunkSize = 250;
|
||||
ChunksDownloadStrategy strategy(servers);
|
||||
strategy.InitChunks(kFileSize, kChunkSize);
|
||||
|
||||
string s1;
|
||||
RangeT r1;
|
||||
TEST_EQUAL(strategy.NextChunk(s1, r1), ChunksDownloadStrategy::ENextChunk, ());
|
||||
|
||||
string s2;
|
||||
RangeT r2;
|
||||
TEST_EQUAL(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENextChunk, ());
|
||||
|
||||
string s3;
|
||||
RangeT r3;
|
||||
TEST_EQUAL(strategy.NextChunk(s3, r3), ChunksDownloadStrategy::ENextChunk, ());
|
||||
|
||||
string sEmpty;
|
||||
RangeT rEmpty;
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
TEST(s1 != s2 && s2 != s3 && s3 != s1, (s1, s2, s3));
|
||||
|
||||
TEST(r1 != r2 && r2 != r3 && r3 != r1, (r1, r2, r3));
|
||||
TEST(r1 == R1 || r1 == R2 || r1 == R3 || r1 == R4, (r1));
|
||||
TEST(r2 == R1 || r2 == R2 || r2 == R3 || r2 == R4, (r2));
|
||||
TEST(r3 == R1 || r3 == R2 || r3 == R3 || r3 == R4, (r3));
|
||||
|
||||
strategy.ChunkFinished(true, r1);
|
||||
|
||||
string s4;
|
||||
RangeT r4;
|
||||
TEST_EQUAL(strategy.NextChunk(s4, r4), ChunksDownloadStrategy::ENextChunk, ());
|
||||
TEST_EQUAL(s4, s1, ());
|
||||
TEST(r4 != r1 && r4 != r2 && r4 != r3, (r4));
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(false, r2);
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(true, r4);
|
||||
|
||||
string s5;
|
||||
RangeT r5;
|
||||
TEST_EQUAL(strategy.NextChunk(s5, r5), ChunksDownloadStrategy::ENextChunk, ());
|
||||
TEST_EQUAL(s5, s4, (s5, s4));
|
||||
TEST_EQUAL(r5, r2, ());
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(true, r5);
|
||||
|
||||
// 3rd is still alive here
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(true, r3);
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::EDownloadSucceeded, ());
|
||||
TEST_EQUAL(strategy.NextChunk(sEmpty, rEmpty), ChunksDownloadStrategy::EDownloadSucceeded, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ChunksDownloadStrategyFAIL)
|
||||
{
|
||||
vector<string> const servers = {"UrlOfServer1", "UrlOfServer2"};
|
||||
|
||||
typedef pair<int64_t, int64_t> RangeT;
|
||||
|
||||
int64_t constexpr kFileSize = 800;
|
||||
int64_t constexpr kChunkSize = 250;
|
||||
ChunksDownloadStrategy strategy(servers);
|
||||
strategy.InitChunks(kFileSize, kChunkSize);
|
||||
|
||||
string s1;
|
||||
RangeT r1;
|
||||
TEST_EQUAL(strategy.NextChunk(s1, r1), ChunksDownloadStrategy::ENextChunk, ());
|
||||
|
||||
string s2;
|
||||
RangeT r2;
|
||||
TEST_EQUAL(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENextChunk, ());
|
||||
TEST_EQUAL(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(false, r1);
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::ENoFreeServers, ());
|
||||
|
||||
strategy.ChunkFinished(false, r2);
|
||||
|
||||
TEST_EQUAL(strategy.NextChunk(s2, r2), ChunksDownloadStrategy::EDownloadFailed, ());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
string ReadFileAsString(string const & file)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileReader f(file);
|
||||
string s;
|
||||
f.ReadAsString(s);
|
||||
return s;
|
||||
}
|
||||
catch (FileReader::Exception const &)
|
||||
{
|
||||
TEST(false, ("File ", file, " should exist"));
|
||||
return string();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FinishDownloadSuccess(string const & file)
|
||||
{
|
||||
TEST(base::DeleteFileX(file), ("Result file should present on success"));
|
||||
|
||||
uint64_t size;
|
||||
TEST(!base::GetFileSize(file + DOWNLOADING_FILE_EXTENSION, size), ("No downloading file on success"));
|
||||
TEST(!base::GetFileSize(file + RESUME_FILE_EXTENSION, size), ("No resume file on success"));
|
||||
}
|
||||
|
||||
void FinishDownloadFail(string const & file)
|
||||
{
|
||||
uint64_t size;
|
||||
TEST(!base::GetFileSize(file, size), ("No result file on fail"));
|
||||
|
||||
(void)base::DeleteFileX(file + DOWNLOADING_FILE_EXTENSION);
|
||||
|
||||
TEST(base::DeleteFileX(file + RESUME_FILE_EXTENSION), ("Resume file should present on fail"));
|
||||
}
|
||||
|
||||
void DeleteTempDownloadFiles()
|
||||
{
|
||||
// Remove data from previously failed files.
|
||||
|
||||
// Get regexp like this: (\.downloading3$|\.resume3$)
|
||||
string const regexp = "(\\" RESUME_FILE_EXTENSION "$|\\" DOWNLOADING_FILE_EXTENSION "$)";
|
||||
|
||||
Platform::FilesList files;
|
||||
Platform::GetFilesByRegExp(".", regexp, files);
|
||||
for (Platform::FilesList::iterator it = files.begin(); it != files.end(); ++it)
|
||||
FileWriter::DeleteFileX(*it);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
UNIT_TEST(DownloadChunks)
|
||||
{
|
||||
string const kFileName = "some_downloader_test_file";
|
||||
|
||||
// remove data from previously failed files
|
||||
DeleteTempDownloadFiles();
|
||||
|
||||
vector<string> urls = {kTestUrl1, kTestUrl1};
|
||||
int64_t fileSize = 5;
|
||||
|
||||
DownloadObserver observer;
|
||||
auto const MakeRequest = [&](int64_t chunkSize)
|
||||
{
|
||||
return HttpRequest::GetFile(urls, kFileName, fileSize,
|
||||
bind(&DownloadObserver::OnDownloadFinish, &observer, _1),
|
||||
bind(&DownloadObserver::OnDownloadProgress, &observer, _1),
|
||||
chunkSize);
|
||||
};
|
||||
|
||||
{
|
||||
// should use only one thread
|
||||
unique_ptr<HttpRequest> const request {MakeRequest(512 * 1024)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
TEST_EQUAL(request->GetData(), kFileName, ());
|
||||
TEST_EQUAL(ReadFileAsString(kFileName), "Test1", ());
|
||||
FinishDownloadSuccess(kFileName);
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
urls = {kTestUrlBigFile, kTestUrlBigFile, kTestUrlBigFile};
|
||||
fileSize = 5;
|
||||
{
|
||||
// 3 threads - fail, because of invalid size
|
||||
[[maybe_unused]] unique_ptr<HttpRequest> const request {MakeRequest(2048)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestFailed();
|
||||
FinishDownloadFail(kFileName);
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
urls = {kTestUrlBigFile, kTestUrlBigFile, kTestUrlBigFile};
|
||||
fileSize = kBigFileSize;
|
||||
{
|
||||
// 3 threads - succeeded
|
||||
[[maybe_unused]] unique_ptr<HttpRequest> const request {MakeRequest(2048)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
FinishDownloadSuccess(kFileName);
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
urls = {kTestUrlBigFile, kTestUrl1, kTestUrl404};
|
||||
fileSize = kBigFileSize;
|
||||
{
|
||||
// 3 threads with only one valid url - succeeded
|
||||
[[maybe_unused]] unique_ptr<HttpRequest> const request {MakeRequest(2048)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestOk();
|
||||
FinishDownloadSuccess(kFileName);
|
||||
}
|
||||
|
||||
observer.Reset();
|
||||
urls = {kTestUrlBigFile, kTestUrlBigFile};
|
||||
fileSize = 12345;
|
||||
{
|
||||
// 2 threads and all points to file with invalid size - fail
|
||||
[[maybe_unused]] unique_ptr<HttpRequest> const request {MakeRequest(2048)};
|
||||
// wait until download is finished
|
||||
QCoreApplication::exec();
|
||||
observer.TestFailed();
|
||||
FinishDownloadFail(kFileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
int64_t constexpr beg1 = 123, end1 = 1230, beg2 = 44000, end2 = 47683;
|
||||
|
||||
struct ResumeChecker
|
||||
{
|
||||
size_t m_counter;
|
||||
ResumeChecker() : m_counter(0) {}
|
||||
|
||||
void OnProgress(HttpRequest & request)
|
||||
{
|
||||
if (m_counter == 0)
|
||||
{
|
||||
TEST_EQUAL(request.GetProgress().m_bytesDownloaded, beg2, ());
|
||||
TEST_EQUAL(request.GetProgress().m_bytesTotal, kBigFileSize, ());
|
||||
}
|
||||
else if (m_counter == 1)
|
||||
{
|
||||
TEST_EQUAL(request.GetProgress().m_bytesDownloaded, kBigFileSize, ());
|
||||
TEST_EQUAL(request.GetProgress().m_bytesTotal, kBigFileSize, ());
|
||||
}
|
||||
else
|
||||
{
|
||||
TEST(false, ("Progress should be called exactly 2 times"));
|
||||
}
|
||||
++m_counter;
|
||||
}
|
||||
|
||||
void OnFinish(HttpRequest & request)
|
||||
{
|
||||
TEST_EQUAL(request.GetStatus(), DownloadStatus::Completed, ());
|
||||
QCoreApplication::exit();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
UNIT_TEST(DownloadResumeChunks)
|
||||
{
|
||||
string const FILENAME = "some_test_filename_12345";
|
||||
string const RESUME_FILENAME = FILENAME + RESUME_FILE_EXTENSION;
|
||||
string const DOWNLOADING_FILENAME = FILENAME + DOWNLOADING_FILE_EXTENSION;
|
||||
|
||||
// remove data from previously failed files
|
||||
DeleteTempDownloadFiles();
|
||||
|
||||
vector<string> urls = {kTestUrlBigFile};
|
||||
|
||||
// 1st step - download full file
|
||||
{
|
||||
DownloadObserver observer;
|
||||
|
||||
unique_ptr<HttpRequest> const request(HttpRequest::GetFile(urls, FILENAME, kBigFileSize,
|
||||
bind(&DownloadObserver::OnDownloadFinish, &observer, _1),
|
||||
bind(&DownloadObserver::OnDownloadProgress, &observer, _1)));
|
||||
|
||||
QCoreApplication::exec();
|
||||
|
||||
observer.TestOk();
|
||||
|
||||
uint64_t size;
|
||||
TEST(!base::GetFileSize(RESUME_FILENAME, size), ("No resume file on success"));
|
||||
}
|
||||
|
||||
// 2nd step - mark some file blocks as not downloaded
|
||||
{
|
||||
// to substitute temporary not fully downloaded file
|
||||
TEST(base::RenameFileX(FILENAME, DOWNLOADING_FILENAME), ());
|
||||
|
||||
FileWriter f(DOWNLOADING_FILENAME, FileWriter::OP_WRITE_EXISTING);
|
||||
f.Seek(beg1);
|
||||
char b1[end1 - beg1 + 1] = {0};
|
||||
f.Write(b1, ARRAY_SIZE(b1));
|
||||
|
||||
f.Seek(beg2);
|
||||
char b2[end2 - beg2 + 1] = {0};
|
||||
f.Write(b2, ARRAY_SIZE(b2));
|
||||
|
||||
ChunksDownloadStrategy strategy((vector<string>()));
|
||||
strategy.AddChunk(make_pair(int64_t(0), beg1-1), ChunksDownloadStrategy::CHUNK_COMPLETE);
|
||||
strategy.AddChunk(make_pair(beg1, end1), ChunksDownloadStrategy::CHUNK_FREE);
|
||||
strategy.AddChunk(make_pair(end1+1, beg2-1), ChunksDownloadStrategy::CHUNK_COMPLETE);
|
||||
strategy.AddChunk(make_pair(beg2, end2), ChunksDownloadStrategy::CHUNK_FREE);
|
||||
|
||||
strategy.SaveChunks(kBigFileSize, RESUME_FILENAME);
|
||||
}
|
||||
|
||||
// 3rd step - check that resume works
|
||||
{
|
||||
ResumeChecker checker;
|
||||
unique_ptr<HttpRequest> const request(HttpRequest::GetFile(urls, FILENAME, kBigFileSize,
|
||||
bind(&ResumeChecker::OnFinish, &checker, _1),
|
||||
bind(&ResumeChecker::OnProgress, &checker, _1)));
|
||||
QCoreApplication::exec();
|
||||
|
||||
FinishDownloadSuccess(FILENAME);
|
||||
}
|
||||
}
|
||||
|
||||
// Unit test with forcible canceling of http request
|
||||
UNIT_TEST(DownloadResumeChunksWithCancel)
|
||||
{
|
||||
string const FILENAME = "some_test_filename_12345";
|
||||
|
||||
// remove data from previously failed files
|
||||
DeleteTempDownloadFiles();
|
||||
|
||||
vector<string> urls = {kTestUrlBigFile};
|
||||
|
||||
DownloadObserver observer;
|
||||
|
||||
int arrCancelChunks[] = { 1, 3, 10, 15, 20, 0 };
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(arrCancelChunks); ++i)
|
||||
{
|
||||
if (arrCancelChunks[i] > 0)
|
||||
observer.CancelDownloadOnGivenChunk(arrCancelChunks[i]);
|
||||
|
||||
unique_ptr<HttpRequest> const request(HttpRequest::GetFile(urls, FILENAME, kBigFileSize,
|
||||
bind(&DownloadObserver::OnDownloadFinish, &observer, _1),
|
||||
bind(&DownloadObserver::OnDownloadProgress, &observer, _1),
|
||||
1024, false));
|
||||
|
||||
QCoreApplication::exec();
|
||||
}
|
||||
|
||||
observer.TestOk();
|
||||
|
||||
FinishDownloadSuccess(FILENAME);
|
||||
}
|
||||
|
||||
} // namespace downloader_test
|
|
@ -1,123 +0,0 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "platform/downloader_utils.hpp"
|
||||
#include "platform/servers_list.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/mwm_version.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
UNIT_TEST(Downloader_GetFilePathByUrl)
|
||||
{
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
std::string const fileName = platform::GetFileName(mwmName, MapFileType::Map);
|
||||
int64_t const dataVersion = version::FOR_TESTING_MWM1;
|
||||
int64_t const diffVersion = 0;
|
||||
MapFileType const fileType = MapFileType::Map;
|
||||
|
||||
auto const path = platform::GetFileDownloadPath(dataVersion, mwmName, fileType);
|
||||
|
||||
auto const url = downloader::GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
auto const resultPath = downloader::GetFilePathByUrl(url);
|
||||
|
||||
TEST_EQUAL(path, resultPath, ());
|
||||
}
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
std::string const fileName = platform::GetFileName(mwmName, MapFileType::Diff);
|
||||
int64_t const dataVersion = version::FOR_TESTING_MWM2;
|
||||
int64_t const diffVersion = version::FOR_TESTING_MWM1;
|
||||
MapFileType const fileType = MapFileType::Diff;
|
||||
|
||||
auto const path = platform::GetFileDownloadPath(dataVersion, mwmName, fileType);
|
||||
|
||||
auto const url = downloader::GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
auto const resultPath = downloader::GetFilePathByUrl(url);
|
||||
|
||||
TEST_EQUAL(path, resultPath, ());
|
||||
}
|
||||
|
||||
TEST_EQUAL(downloader::GetFilePathByUrl("/maps/220314/Belarus_Brest Region.mwm"),
|
||||
base::JoinPath(GetPlatform().WritableDir(), "220314/Belarus_Brest Region.mwm.ready"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Downloader_IsUrlSupported)
|
||||
{
|
||||
std::string const mwmName = "Luna";
|
||||
|
||||
std::string fileName = platform::GetFileName(mwmName, MapFileType::Map);
|
||||
int64_t dataVersion = version::FOR_TESTING_MWM1;
|
||||
int64_t diffVersion = 0;
|
||||
|
||||
auto url = downloader::GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
TEST(downloader::IsUrlSupported(url), ());
|
||||
TEST(downloader::IsUrlSupported("maps/991215/Luna.mwm"), ());
|
||||
TEST(downloader::IsUrlSupported("maps/0/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("maps/x/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("macarena/0/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("/hack/maps/0/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("0/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("maps/0/Luna"), ());
|
||||
TEST(!downloader::IsUrlSupported("0/Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("Luna.mwm"), ());
|
||||
TEST(!downloader::IsUrlSupported("Luna"), ());
|
||||
|
||||
fileName = platform::GetFileName(mwmName, MapFileType::Diff);
|
||||
diffVersion = version::FOR_TESTING_MWM1;
|
||||
url = downloader::GetFileDownloadUrl(fileName, dataVersion, diffVersion);
|
||||
TEST(downloader::IsUrlSupported(url), ());
|
||||
TEST(downloader::IsUrlSupported("diffs/991215/991215/Luna.mwmdiff"), ());
|
||||
TEST(downloader::IsUrlSupported("diffs/0/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/x/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0/x/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/x/x/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("beefs/0/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0/0/Luna.mwmdiff.f"), ());
|
||||
TEST(!downloader::IsUrlSupported("maps/diffs/0/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0/0/Luna"), ());
|
||||
TEST(!downloader::IsUrlSupported("0/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/0/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("diffs/Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("Luna.mwmdiff"), ());
|
||||
TEST(!downloader::IsUrlSupported("Luna"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Downloader_ParseMetaConfig)
|
||||
{
|
||||
std::optional<downloader::MetaConfig> cfg;
|
||||
|
||||
TEST((cfg = downloader::ParseMetaConfig(R"([ "https://url1/", "https://url2/" ])")), ());
|
||||
TEST_EQUAL(cfg->m_serversList.size(), 2, ());
|
||||
TEST_EQUAL(cfg->m_serversList[0], "https://url1/", ());
|
||||
TEST_EQUAL(cfg->m_serversList[1], "https://url2/", ());
|
||||
|
||||
TEST((cfg = downloader::ParseMetaConfig(R"(
|
||||
{
|
||||
"servers": [ "https://url1/", "https://url2/" ],
|
||||
"settings": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
}
|
||||
)")), ());
|
||||
TEST_EQUAL(cfg->m_serversList.size(), 2, ());
|
||||
TEST_EQUAL(cfg->m_serversList[0], "https://url1/", ());
|
||||
TEST_EQUAL(cfg->m_serversList[1], "https://url2/", ());
|
||||
TEST_EQUAL(cfg->m_settings.size(), 2, ());
|
||||
TEST_EQUAL(cfg->m_settings["key1"], "value1", ());
|
||||
TEST_EQUAL(cfg->m_settings["key2"], "value2", ());
|
||||
|
||||
TEST(!downloader::ParseMetaConfig(R"(broken json)"), ());
|
||||
|
||||
TEST(!downloader::ParseMetaConfig(R"([])"), ());
|
||||
|
||||
TEST(!downloader::ParseMetaConfig(R"({})"), ());
|
||||
|
||||
TEST(!downloader::ParseMetaConfig(R"({"no_servers": "invalid"})"), ());
|
||||
|
||||
TEST(!downloader::ParseMetaConfig(R"({"servers": "invalid"})"), ());
|
||||
}
|
|
@ -9,8 +9,6 @@ set(SRC
|
|||
scoped_file.hpp
|
||||
scoped_mwm.cpp
|
||||
scoped_mwm.hpp
|
||||
test_socket.cpp
|
||||
test_socket.hpp
|
||||
writable_dir_changer.cpp
|
||||
writable_dir_changer.hpp
|
||||
)
|
||||
|
|
Dropping the internal folder may simplify the structure.