diff --git a/KeeneticPlugin.cpp b/KeeneticPlugin.cpp index 4018f79..3a93a5c 100644 --- a/KeeneticPlugin.cpp +++ b/KeeneticPlugin.cpp @@ -1,290 +1,341 @@ -#include - -#include -#include - -#include -#include "Core/Network/NetworkClient.h" -#include "API/RainmeterAPI.h" -#include "Core/Utils/CryptoUtils.h" - -enum class MeasureType -{ - mtDownload, - mtUpload -}; - -LPCWSTR rmDataFile = nullptr; -class Worker; - -struct Settings{ - std::string routerUrl; - std::string login; - std::string password; - std::string proxy; - int proxyPort = 0; -}; - -class SettingsLoader -{ -public: - static std::unique_ptr loadSettings(void* rm, LPCTSTR configFile) { - WCHAR loginW[256]{}; - WCHAR passwordW[256]{}; - WCHAR urlW[256]{}; - WCHAR proxyW[256]{}; - - GetPrivateProfileString(L"KeeneticPlugin", L"URL", L"http://192.168.1.1", urlW, 256, rmDataFile); - GetPrivateProfileString(L"KeeneticPlugin", L"Login", L"admin", loginW, 256, rmDataFile); - GetPrivateProfileString(L"KeeneticPlugin", L"Password", L"", passwordW, 256, rmDataFile); - GetPrivateProfileString(L"KeeneticPlugin", L"Proxy", L"", proxyW, 256, rmDataFile); - - if (!lstrlen(passwordW)) { - RmLog(rm, LOG_ERROR, (std::wstring(L"No password set for KeeneticPlugin in config file ") + rmDataFile).c_str()); - return {}; - } - - std::unique_ptr res = std::make_unique(); - - res->proxyPort = GetPrivateProfileInt(L"KeeneticPlugin", L"ProxyPort", 8080, rmDataFile); - - res->routerUrl = IuCoreUtils::WstringToUtf8(urlW); - res->login = IuCoreUtils::WstringToUtf8(loginW); - res->password = IuCoreUtils::WstringToUtf8(passwordW); - res->proxy = IuCoreUtils::WstringToUtf8(proxyW); - - return res; - } -}; - -class Worker -{ -public: - Worker(void *rm, std::shared_ptr settings) { - stopSignal = false; - rm_ = rm; - settings_ = std::move(settings); - } - - void start() { - if (started_) { - return; - } - thread_ = std::thread(&Worker::run, this); - } - - void run() { - nc_ = std::make_unique(); - if (!settings_->proxy.empty() && settings_->proxyPort > 0) { - nc_->setProxy(settings_->proxy, settings_->proxyPort, CURLPROXY_HTTP); - } - - nc_->setCurlOptionInt(CURLOPT_CONNECTTIMEOUT, 5); - - while (!stopSignal) { - if (!authenticated) { - if (!authenticate()) { - Sleep(1000); - continue; - } - } - if (stopSignal) { - break; - } - loadData(); - Sleep(1000); - } - logout(); - nc_ = nullptr; - } - - void abort() { - stopSignal = true; - if (thread_.joinable()) { - thread_.join(); - } - } - - void setProxyPort(int port) { - proxyPort_ = port; - } - - double getUploadSpeed() const { - return uploadSpeed_; - } - - double getDownloadSpeed() const { - return downloadSpeed_; - } - -private: - std::shared_ptr settings_; - void* rm_; - bool started_ = false; - std::thread thread_; - std::unique_ptr nc_; - ULONGLONG lastAuthErrorTime_ = 0; - - int proxyPort_ = 0; - std::atomic_bool stopSignal = false; - std::mutex dataMutex_; - - std::atomic uploadSpeed_ = 0.0; - std::atomic downloadSpeed_ = 0.0; - - bool authenticated = false; - - bool authenticate() { - if (lastAuthErrorTime_ && (GetTickCount64() - lastAuthErrorTime_ < 10000)) { - return false; - } - nc_->doGet(settings_->routerUrl + "/auth"); - - std::string challenge = nc_->responseHeaderByName("X-NDM-Challenge"); - - - std::string realm = nc_->responseHeaderByName("X-NDM-Realm"); - - if (challenge.empty() || realm.empty()) { - std::wstring msg = std::wstring(L"Failed to obtain realm token. Response code : ") - + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); - RmLog(rm_, LOG_ERROR, msg.c_str()); - return false; - } - nc_->setUrl(settings_->routerUrl + "/auth"); - - std::string hash = IuCoreUtils::CryptoUtils::CalcSHA256HashFromString(challenge + - IuCoreUtils::CryptoUtils::CalcMD5HashFromString(settings_->login + ":" + realm + ":" + settings_->password) - ); - - Json::Value val; - val["login"] = settings_->login; - val["password"] = hash; - - std::ostringstream stream; - stream << val; - - nc_->addQueryHeader("Content-Type", "application/json"); - nc_->doPost(stream.str()); - - if (nc_->responseCode() != 200) { - std::wstring msg = std::wstring(L"Authentication failed on router. Response code : ") - + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); - RmLog(rm_, LOG_ERROR, msg.c_str()); - lastAuthErrorTime_ = GetTickCount64(); - return false; - } - - authenticated = true; - return true; - } - - bool logout() { - nc_->setUrl(settings_->routerUrl + "/auth"); - nc_->setMethod("DELETE"); - nc_->doPost({}); - - return nc_->responseCode() == 200; - } - - void loadData() { - bool success = false; - nc_->setUrl(settings_->routerUrl + "/rci/"); - std::string s = - R"({"show":{"interface":{"rrd":[{"name":"ISP","attribute":"rxspeed","detail":0},{"name":"ISP","attribute":"txspeed","detail":0}]}}})"; - nc_->addQueryHeader("Content-Type", "application/json"); - nc_->doPost(s); - - if (nc_->responseCode() == 200) { - Json::Value val; - Json::Reader reader; - if (reader.parse(nc_->responseBody(), val, false)) { - Json::Value rrd = val["show"]["interface"]["rrd"]; - //int index = measure->mt == MeasureType::mtUpload ? 1 : 0; - std::unique_lock lk(dataMutex_); - Json::Value data2 = rrd[0]["data"]; - if (!data2.empty()) { - Json::Value download = *data2.begin(); - downloadSpeed_ = download["v"].asDouble() / 1000000.0; - } - - Json::Value data3 = rrd[1]["data"]; - if (!data3.empty()) { - Json::Value upload = *data3.begin(); - uploadSpeed_ = upload["v"].asDouble() / 1000000.0; - } - success = true; - } - } - else { - if (nc_->responseCode() == 401) { - authenticated = false; - } - std::wstring msg = std::wstring(L"Failed to get data from router. Response code: ") - + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); - - RmLog(rm_, LOG_ERROR, msg.c_str()); - } - - if (!success) { - downloadSpeed_ = 0.0; - uploadSpeed_ = 0.0; - } - } -}; - -struct Measure { - MeasureType mt = MeasureType::mtDownload; - void* rm = nullptr; - std::shared_ptr worker; -}; -std::shared_ptr worker; - - -PLUGIN_EXPORT void Initialize(void** data, void* rm) { - - auto* measure = new Measure; - *data = measure; - - if (rmDataFile == nullptr) { - rmDataFile = RmGetSettingsFile(); - } - - if (!worker) { - std::shared_ptr settings = SettingsLoader::loadSettings(rm, rmDataFile); - if (!settings) { - return; - } - worker = std::make_shared(rm, settings); - worker->start(); - } - measure->worker = worker; -} - -PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { - auto* measure = static_cast(data); - - LPCWSTR value = RmReadString(rm, L"Type", L"download"); - - if (value) { - std::wstring val = value; - measure->mt = val == L"upload" ? MeasureType::mtUpload : MeasureType::mtDownload; - } -} - -PLUGIN_EXPORT double Update(void* data) { - auto* measure = static_cast(data); - if (!measure->worker) { - return {}; - } - return measure->mt == MeasureType::mtDownload ? measure->worker->getDownloadSpeed() : measure->worker->getUploadSpeed(); -} - -PLUGIN_EXPORT void Finalize(void* data) { - auto* measure = static_cast(data); - if (measure->worker) { - measure->worker->abort(); - } - delete measure; - worker.reset(); -} +#include + +#include +#include + +#include +#include "Core/Network/NetworkClient.h" +#include "API/RainmeterAPI.h" +#include "Core/Utils/CryptoUtils.h" + +enum class MeasureType +{ + mtDownload, + mtUpload +}; + +LPCWSTR rmDataFile = nullptr; +class Worker; + +struct Settings{ + std::string routerUrl; + std::string login; + std::string password; + std::string proxy; + std::string interf; + int proxyPort = 0; +}; + +class SettingsLoader +{ +public: + static std::unique_ptr loadSettings(void* rm, LPCTSTR configFile) { + WCHAR loginW[256]{}; + WCHAR passwordW[256]{}; + WCHAR urlW[256]{}; + WCHAR proxyW[256]{}; + WCHAR interfaceW[256]{}; + + GetPrivateProfileString(L"KeeneticPlugin", L"URL", L"http://192.168.1.1", urlW, 256, rmDataFile); + GetPrivateProfileString(L"KeeneticPlugin", L"Login", L"admin", loginW, 256, rmDataFile); + GetPrivateProfileString(L"KeeneticPlugin", L"Password", L"", passwordW, 256, rmDataFile); + GetPrivateProfileString(L"KeeneticPlugin", L"Proxy", L"", proxyW, 256, rmDataFile); + GetPrivateProfileString(L"KeeneticPlugin", L"Interface", L"ISP", interfaceW, 256, rmDataFile); + + if (!lstrlen(passwordW)) { + RmLog(rm, LOG_ERROR, (std::wstring(L"No password set for KeeneticPlugin in config file ") + rmDataFile).c_str()); + return {}; + } + + std::unique_ptr res = std::make_unique(); + + res->proxyPort = GetPrivateProfileInt(L"KeeneticPlugin", L"ProxyPort", 8080, rmDataFile); + + res->routerUrl = IuCoreUtils::WstringToUtf8(urlW); + res->login = IuCoreUtils::WstringToUtf8(loginW); + res->password = IuCoreUtils::WstringToUtf8(passwordW); + res->proxy = IuCoreUtils::WstringToUtf8(proxyW); + res->interf = IuCoreUtils::WstringToUtf8(interfaceW); + return res; + } +}; + +class Worker +{ +public: + Worker(void *rm, std::shared_ptr settings) { + stopSignal = false; + rm_ = rm; + settings_ = std::move(settings); + } + + void start() { + if (started_) { + return; + } + thread_ = std::thread(&Worker::run, this); + } + + void run() { + nc_ = std::make_unique(); + if (!settings_->proxy.empty() && settings_->proxyPort > 0) { + nc_->setProxy(settings_->proxy, settings_->proxyPort, CURLPROXY_HTTP); + } + + nc_->setCurlOptionInt(CURLOPT_CONNECTTIMEOUT, 5); + + while (!stopSignal) { + if (!authenticated) { + if (!authenticate()) { + Sleep(1000); + continue; + } + } + if (stopSignal) { + break; + } + loadData(); + Sleep(1000); + } + logout(); + nc_ = nullptr; + } + + void abort() { + stopSignal = true; + if (thread_.joinable()) { + thread_.join(); + } + } + + void setProxyPort(int port) { + proxyPort_ = port; + } + + double getUploadSpeed() const { + return uploadSpeed_; + } + + double getDownloadSpeed() const { + return downloadSpeed_; + } + +private: + std::shared_ptr settings_; + void* rm_; + bool started_ = false; + std::thread thread_; + std::unique_ptr nc_; + ULONGLONG lastAuthErrorTime_ = 0; + + int proxyPort_ = 0; + std::atomic_bool stopSignal = false; + std::mutex dataMutex_; + + std::atomic uploadSpeed_ = 0.0; + std::atomic downloadSpeed_ = 0.0; + + bool authenticated = false; + + bool authenticate() { + if (lastAuthErrorTime_ && (GetTickCount64() - lastAuthErrorTime_ < 10000)) { + return false; + } + nc_->doGet(settings_->routerUrl + "/auth"); + + std::string challenge = nc_->responseHeaderByName("X-NDM-Challenge"); + + + std::string realm = nc_->responseHeaderByName("X-NDM-Realm"); + + if (challenge.empty() || realm.empty()) { + std::wstring msg = std::wstring(L"Failed to obtain realm token. Response code : ") + + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); + RmLog(rm_, LOG_ERROR, msg.c_str()); + return false; + } + nc_->setUrl(settings_->routerUrl + "/auth"); + + std::string hash = IuCoreUtils::CryptoUtils::CalcSHA256HashFromString(challenge + + IuCoreUtils::CryptoUtils::CalcMD5HashFromString(settings_->login + ":" + realm + ":" + settings_->password) + ); + + Json::Value val; + val["login"] = settings_->login; + val["password"] = hash; + + std::ostringstream stream; + stream << val; + + nc_->addQueryHeader("Content-Type", "application/json"); + nc_->doPost(stream.str()); + + if (nc_->responseCode() != 200) { + std::wstring msg = std::wstring(L"Authentication failed on router. Response code : ") + + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); + RmLog(rm_, LOG_ERROR, msg.c_str()); + lastAuthErrorTime_ = GetTickCount64(); + return false; + } + + authenticated = true; + return true; + } + + bool logout() { + nc_->setUrl(settings_->routerUrl + "/auth"); + nc_->setMethod("DELETE"); + nc_->doPost({}); + + return nc_->responseCode() == 200; + } + + void loadData() { + bool success = false; + nc_->setUrl(settings_->routerUrl + "/rci/"); + + Json::Value rrd1; + rrd1["name"] = settings_->interf; + rrd1["attribute"] = "rxspeed"; + rrd1["detail"] = 0; + + Json::Value rrd2; + rrd2["name"] = settings_->interf; + rrd2["attribute"] = "txspeed"; + rrd2["detail"] = 0; + + Json::Value rrd(Json::arrayValue); + rrd.append(rrd1); + rrd.append(rrd2); + + Json::Value root; + root["show"]["interface"]["rrd"] = rrd; + + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; + std::string s = Json::writeString(builder, root); + + /*std::string s = + R"({"show":{"interface":{"rrd":[{"name":"ISP","attribute":"rxspeed","detail":0},{"name":"ISP","attribute":"txspeed","detail":0}]}}})"; + */ + + nc_->addQueryHeader("Content-Type", "application/json"); + nc_->doPost(s); + + if (nc_->responseCode() == 200) { + Json::Value val; + Json::Reader reader; + if (reader.parse(nc_->responseBody(), val, false)) { + try { + Json::Value& rrdObj = val["show"]["interface"]["rrd"]; + if (rrdObj.isArray()) { + //int index = measure->mt == MeasureType::mtUpload ? 1 : 0; + std::unique_lock lk(dataMutex_); + + Json::Value& status = rrdObj[0]["status"]; + + if (status.isArray() && status[0]["status"] == "error") { + std::wstring msg = std::wstring(L"Server answered with error: ") + + IuCoreUtils::Utf8ToWstring(status[0]["message"].asCString()); + RmLog(rm_, LOG_ERROR, msg.c_str()); + } + + Json::Value& data2 = rrdObj[0]["data"]; + + if (!data2.empty()) { + Json::Value download = *data2.begin(); + downloadSpeed_ = download["v"].asDouble() / 1000000.0; + } + + Json::Value& status2 = rrdObj[1]["status"]; + if (status2.isArray() && status2[0]["status"] == "error") { + std::wstring msg = std::wstring(L"Server answered with error: ") + + IuCoreUtils::Utf8ToWstring(status2[0]["message"].asCString()); + RmLog(rm_, LOG_ERROR, msg.c_str()); + } + + Json::Value& data3 = rrdObj[1]["data"]; + if (!data3.empty()) { + Json::Value upload = *data3.begin(); + uploadSpeed_ = upload["v"].asDouble() / 1000000.0; + } + success = true; + } + } catch (const std::exception& ex) { + RmLog(rm_, LOG_ERROR, IuCoreUtils::Utf8ToWstring(ex.what()).c_str()); + } + } + } + else { + if (nc_->responseCode() == 401) { + authenticated = false; + } + std::wstring msg = std::wstring(L"Failed to get data from router. Response code: ") + + std::to_wstring(nc_->responseCode()) + L", CURL error: " + IuCoreUtils::Utf8ToWstring(nc_->errorString()); + + RmLog(rm_, LOG_ERROR, msg.c_str()); + } + + if (!success) { + downloadSpeed_ = 0.0; + uploadSpeed_ = 0.0; + } + } +}; + +struct Measure { + MeasureType mt = MeasureType::mtDownload; + void* rm = nullptr; + std::shared_ptr worker; +}; +std::shared_ptr worker; + + +PLUGIN_EXPORT void Initialize(void** data, void* rm) { + + auto* measure = new Measure; + *data = measure; + + if (rmDataFile == nullptr) { + rmDataFile = RmGetSettingsFile(); + } + + if (!worker) { + std::shared_ptr settings = SettingsLoader::loadSettings(rm, rmDataFile); + if (!settings) { + return; + } + worker = std::make_shared(rm, settings); + worker->start(); + } + measure->worker = worker; +} + +PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { + auto* measure = static_cast(data); + + LPCWSTR value = RmReadString(rm, L"Type", L"download"); + + if (value) { + std::wstring val = value; + measure->mt = val == L"upload" ? MeasureType::mtUpload : MeasureType::mtDownload; + } +} + +PLUGIN_EXPORT double Update(void* data) { + auto* measure = static_cast(data); + if (!measure->worker) { + return {}; + } + return measure->mt == MeasureType::mtDownload ? measure->worker->getDownloadSpeed() : measure->worker->getUploadSpeed(); +} + +PLUGIN_EXPORT void Finalize(void* data) { + auto* measure = static_cast(data); + if (measure->worker) { + measure->worker->abort(); + } + delete measure; + worker.reset(); +} diff --git a/KeeneticPlugin.rc b/KeeneticPlugin.rc index 9ff5210..cb3f9dc 100644 --- a/KeeneticPlugin.rc +++ b/KeeneticPlugin.rc @@ -1,43 +1,43 @@ -#define APSTUDIO_READONLY_SYMBOLS -#include -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,5,0 - PRODUCTVERSION 3,0,2,2161 - FILEFLAGSMASK 0x17L -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - BEGIN - VALUE "FileVersion", "1.0.5.0" - VALUE "LegalCopyright", "© 2021 - Sergey Svistunov" - - // Don't change the entries below! - VALUE "ProductName", "Rainmeter" -#ifdef _WIN64 - VALUE "ProductVersion", "3.0.2.2161 (64-bit)" -#else - VALUE "ProductVersion", "3.0.2.2161 (32-bit)" -#endif //_WIN64 - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END +#define APSTUDIO_READONLY_SYMBOLS +#include +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,6,0 + PRODUCTVERSION 3,0,2,2161 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "FileVersion", "1.0.6.0" + VALUE "LegalCopyright", "© 2021 - Sergey Svistunov" + + // Don't change the entries below! + VALUE "ProductName", "Rainmeter" +#ifdef _WIN64 + VALUE "ProductVersion", "3.0.2.2161 (64-bit)" +#else + VALUE "ProductVersion", "3.0.2.2161 (32-bit)" +#endif //_WIN64 + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/README.md b/README.md index 510c5d1..7edd26f 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,66 @@ -# KeeneticRainmeterPlugin -Keenetic router's speed graph directly on your Windows desktop - -![KeeneticRainmeterPlugin screenshot](https://i.imgur.com/UhVCydL.png) - - -## Installation - -First you have to install [Rainmeter](https://www.rainmeter.net/) on your computer. Then download and install .rmskin file which is available in the [Releases](https://github.com/zenden2k/KeeneticRainmeterPlugin/releases) section. - -After installation you should specify password of your Keenetic user in "Rainmeter.data" file (which is placed by default in "C:\Users\user\AppData\Roaming\Rainmeter" folder). -Also you can specify the username and URL of the router web interface. - -```ini -[KeeneticPlugin] -Login=admin -Password=qwerty1234 -URL=http://192.168.1.1 -``` - -Plugin has been tested with Keenetic Viva (KN-1910). - - -## Building from sources - -To build this plugin from source files you will need: - -- Git https://git-scm.com/downloads -- Microsoft Visual Studio 2019 or newer (with C++ compiler) https://visualstudio.microsoft.com/downloads/ -- Python 3 https://www.python.org/downloads/ -- Conan (C++ package manager) https://conan.io/ - -## Building dependencies - -Run the following commands: - -```bash -conan install . -g visual_studio_multi -s arch=x86 -s build_type=Release --build=missing -s compiler.runtime=MT -conan install . -g visual_studio_multi -s arch=x86_64 -s build_type=Release --build=missing -s compiler.runtime=MT - -conan install . -g visual_studio_multi -s arch=x86 -s build_type=Debug --build=missing -s compiler.runtime=MTd -conan install . -g visual_studio_multi -s arch=x86_64 -s build_type=Debug --build=missing -s compiler.runtime=MTd -``` - -You can now load conanbuildinfo_multi.props in your Visual Studio IDE property manager, and all configurations will be loaded at once. - -Dependencies - -- libcurl https://github.com/curl/curl -- jsoncpp https://github.com/open-source-parsers/jsoncpp +# KeeneticRainmeterPlugin +Keenetic router's speed graph directly on your Windows desktop + +![KeeneticRainmeterPlugin screenshot](https://i.imgur.com/UhVCydL.png) + + +## Installation + +First you have to install [Rainmeter](https://www.rainmeter.net/) on your computer. Then download and install .rmskin file which is available in the [Releases](https://github.com/zenden2k/KeeneticRainmeterPlugin/releases) section. + +After installation you should specify password of your Keenetic user in "Rainmeter.data" file (which is placed by default in "C:\Users\user\AppData\Roaming\Rainmeter" folder). +Also you can specify the username and URL of the router web interface. + +```ini +[KeeneticPlugin] +Login=admin +Password=qwerty1234 +URL=http://192.168.1.1 +Interface=ISP +``` + +Plugin has been tested with Keenetic Viva (KN-1910). + +## Obtaining interface name (optional) + +Connect to your router with **telnet**: + +``` +telnet 192.168.1.1 +``` +and run command + +``` +show interface +``` + +All interfaces will be listed. + +## Building from sources + +To build this plugin from source files you will need: + +- Git https://git-scm.com/downloads +- Microsoft Visual Studio 2019 or newer (with C++ compiler) https://visualstudio.microsoft.com/downloads/ +- Python 3 https://www.python.org/downloads/ +- Conan (C++ package manager) https://conan.io/ + +## Building dependencies + +Run the following commands: + +```bash +conan install . -g visual_studio_multi -s arch=x86 -s build_type=Release --build=missing -s compiler.runtime=MT +conan install . -g visual_studio_multi -s arch=x86_64 -s build_type=Release --build=missing -s compiler.runtime=MT + +conan install . -g visual_studio_multi -s arch=x86 -s build_type=Debug --build=missing -s compiler.runtime=MTd +conan install . -g visual_studio_multi -s arch=x86_64 -s build_type=Debug --build=missing -s compiler.runtime=MTd +``` + +You can now load conanbuildinfo_multi.props in your Visual Studio IDE property manager, and all configurations will be loaded at once. + +Dependencies + +- libcurl https://github.com/curl/curl +- jsoncpp https://github.com/open-source-parsers/jsoncpp - utf8 https://github.com/nemtrif/utfcpp \ No newline at end of file diff --git a/conanfile.txt b/conanfile.txt index 8059d59..4cdbfd7 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,12 +1,12 @@ -[requires] -libcurl/7.84.0 -jsoncpp/1.9.5 -utfcpp/3.2.1 - -[generators] -visual_studio_multi - -[options] -libcurl:with_ssl=schannel -libcurl:shared=False +[requires] +libcurl/7.87.0 +jsoncpp/1.9.5 +utfcpp/3.2.1 + +[generators] +visual_studio_multi + +[options] +libcurl:with_ssl=schannel +libcurl:shared=False jsoncpp:shared=False \ No newline at end of file