diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8c668c5..949a068 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,7 +40,7 @@ jobs: uses: johnwason/vcpkg-action@v6 id: vcpkg with: - pkgs: curl gettext-libintl glib gtest jsoncpp libsecret maddy openssl + pkgs: boost-json curl gettext-libintl glib gtest libsecret maddy openssl triplet: x64-linux revision: a4cfba036f013aea0347c3efc335186754b696b3 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index cc0f6e8..503dcf2 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,7 +29,7 @@ jobs: uses: johnwason/vcpkg-action@v6 id: vcpkg with: - pkgs: curl gettext-libintl glib gtest jsoncpp maddy openssl + pkgs: boost-json curl gettext-libintl glib gtest maddy openssl triplet: arm64-osx revision: a4cfba036f013aea0347c3efc335186754b696b3 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ad842f4..d17e19f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,7 +27,7 @@ jobs: uses: johnwason/vcpkg-action@v6 id: vcpkg with: - pkgs: curl gettext-libintl gtest jsoncpp maddy sqlcipher + pkgs: boost-json curl gettext-libintl gtest maddy sqlcipher triplet: x64-windows revision: a4cfba036f013aea0347c3efc335186754b696b3 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf3418..6523e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 2024.9.0 (next) +### Breaking Changes +- Replaced `jsoncpp` library with `boost-json` +#### App +- `Nickvision::App::DataFileBase`'s `m_json` object is now of type `boost::json::object` +### New APIs +None +### Fixes +None + ## 2024.8.3 ### Breaking Changes #### Keyring diff --git a/CMakeLists.txt b/CMakeLists.txt index 0af67d2..b69f5dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") #libnick Definition -project ("libnick" LANGUAGES C CXX VERSION 2024.8.3 DESCRIPTION "A cross-platform base for native Nickvision applications.") +project ("libnick" LANGUAGES C CXX VERSION 2024.9.0 DESCRIPTION "A cross-platform base for native Nickvision applications.") include(CMakePackageConfigHelpers) include(GNUInstallDirs) include(CTest) @@ -83,10 +83,10 @@ else() endif() #libnick Packages +find_package(Boost REQUIRED COMPONENTS json) find_package(CURL REQUIRED) -find_package(jsoncpp CONFIG REQUIRED) find_package(Intl REQUIRED) -target_link_libraries(${PROJECT_NAME} PUBLIC CURL::libcurl JsonCpp::JsonCpp Intl::Intl) +target_link_libraries(${PROJECT_NAME} PUBLIC Boost::json CURL::libcurl Intl::Intl) if(USING_VCPKG) find_package(unofficial-maddy CONFIG REQUIRED) target_link_libraries(${PROJECT_NAME} PUBLIC unofficial::maddy::maddy) diff --git a/Doxyfile b/Doxyfile index a822640..babae36 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = "libnick" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "2024.8.3" +PROJECT_NUMBER = "2024.9.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/README.md b/README.md index be53095..bde3f79 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Documentation for this library and its modules can be found [here](https://nickv The following are a list of dependencies used by libnick. ### All Platforms +- boost-json - gtest -- jsoncpp - libcurl - libintl - maddy @@ -62,16 +62,16 @@ A C++20 compiler is also required to build libnick. 1. Set the `VCPKG_ROOT` environment variable to the path of your vcpkg installation's root directory. #### Windows 1. Set the `VCPKG_DEFAULT_TRIPLET` environment variable to `x64-windows` -1. Run `vcpkg install curl gettext-libintl gtest jsoncpp maddy sqlcipher` +1. Run `vcpkg install boost-json curl gettext-libintl gtest maddy sqlcipher` #### Linux 1. Set the `VCPKG_DEFAULT_TRIPLET` environment variable to `x64-linux` -1. Run `vcpkg install curl gettext-libintl glib gtest jsoncpp libsecret maddy openssl` +1. Run `vcpkg install boost-json curl gettext-libintl glib gtest libsecret maddy openssl` #### macOS (Intel) 1. Set the `VCPKG_DEFAULT_TRIPLET` environment variable to `x64-osx` -1. Run `vcpkg install curl gettext-libintl glib gtest jsoncpp libsecret maddy openssl` +1. Run `vcpkg install boost-json curl gettext-libintl glib gtest libsecret maddy openssl` #### macOS (Apple Silicon) 1. Set the `VCPKG_DEFAULT_TRIPLET` environment variable to `arm64-osx` -1. Run `vcpkg install curl gettext-libintl glib gtest jsoncpp libsecret maddy openssl` +1. Run `vcpkg install boost-json curl gettext-libintl glib gtest libsecret maddy openssl` ### Building 1. First, clone/download the repo. diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index a1e310d..6590724 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -10,8 +10,8 @@ if(DEFINED ENV{VCPKG_ROOT}) endif() endif() +find_dependency(Boost REQUIRED COMPONENTS json) find_dependency(CURL REQUIRED) -find_dependency(jsoncpp CONFIG REQUIRED) find_dependency(Intl REQUIRED) if(USING_VCPKG) find_dependency(unofficial-maddy REQUIRED) diff --git a/include/app/datafilebase.h b/include/app/datafilebase.h index c5d386f..8faa0fa 100644 --- a/include/app/datafilebase.h +++ b/include/app/datafilebase.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include "events/event.h" namespace Nickvision::App @@ -65,7 +65,7 @@ namespace Nickvision::App bool save(); protected: - Json::Value m_json; + mutable boost::json::object m_json; private: std::string m_key; diff --git a/include/network/web.h b/include/network/web.h index 4517a7a..63ca693 100644 --- a/include/network/web.h +++ b/include/network/web.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include "curleasy.h" namespace Nickvision::Network::Web @@ -39,9 +39,9 @@ namespace Nickvision::Network::Web /** * @brief Fetches a json string from a url. * @param url The url of the json string - * @return The fetched json object + * @return The fetched json value */ - Json::Value fetchJson(const std::string& url); + boost::json::value fetchJson(const std::string& url); /** * @brief Downloads a file to disk. * @param url The url of the file to download diff --git a/manual/README.md b/manual/README.md index 275c775..7dfacde 100644 --- a/manual/README.md +++ b/manual/README.md @@ -6,12 +6,9 @@ libnick provides Nickvision apps with a common set of cross-platform APIs for managing system and desktop app functionality such as network management, taskbar icons, translations, app updates, and more. -## 2024.8.3 +## 2024.9.0 (next) ### Breaking Changes -#### Keyring -- The `Nickvision::Keyring::Keyring` class has been rewritten for better performance and a cleaner API. Keyrings created with previous versions of libnick are no longer compatible. -- Removed `Nickvision::Keyring::KeyringDialogController` -- Removed `Nickvision::Keyring::Store` +- Replaced `jsoncpp` library with `boost-json` ### New APIs None ### Fixes diff --git a/manual/datafiles.md b/manual/datafiles.md index e5aad58..041e51b 100644 --- a/manual/datafiles.md +++ b/manual/datafiles.md @@ -6,7 +6,7 @@ Here are some key points when defining your own configuration objects: - Although you will not use `key` and `appName` in your own implementation, it is required for `DataFileBase`'s functionality and will be filled-in by the `DataFileManager`. - `DataFileBase` exposes a protected `m_json` object which you must use in your implementation of getting and storing variables of your data object. - If this `m_json` object is not used, your data object will not be stored to disk correctly. -- You must explicitly call the `save` method on your configuration object when you want to save the configuration to disk. Writing to the `m_json` object is not enough to trigger saving the file on disk. +- You must explicitly call the `save` method on your configuration object when you want to save the configuration to disk. Writing to the `m_json` (of type `boost::json::object`) object is not enough to trigger saving the file on disk. Here is an example of a custom configuration object using `DataFileBase`: ```cpp @@ -16,22 +16,26 @@ using namespace Nickvision::Events; class AppConfig : public DataFileBase { public: - AppConfig(const std::string& key, const std::string& appName) - : DataFileBase{ key, appName } - { - - } - - int getPreviousCount() const - { - //0 is the default value of PreviousCount (i.e. if it does not exist in the file) - return m_json.get("PreviousCount", 0).asInt(); - } - - void setPreviousCount(int count) - { - m_json["PreviousCount"] = count; - } + AppConfig(const std::string& key, const std::string& appName) + : DataFileBase{ key, appName } + { + + } + + bool getAutomaticallyCheckForUpdates() const + { + const boost::json::value& value{ m_json["AutomaticallyCheckForUpdates"] }; + if(!value.is_bool()) + { + return true; + } + return value.as_bool(); + } + + void setAutomaticallyCheckForUpdates(bool value) + { + m_json["AutomaticallyCheckForUpdates"] = value; + } }; //This object can now be used with the DataFileManager: diff --git a/src/app/datafilebase.cpp b/src/app/datafilebase.cpp index d656b3a..7a1c4c7 100644 --- a/src/app/datafilebase.cpp +++ b/src/app/datafilebase.cpp @@ -21,8 +21,23 @@ namespace Nickvision::App m_path = UserDirectories::get(ApplicationUserDirectory::Config, appName) / (m_key + ".json"); if (std::filesystem::exists(m_path)) { - std::ifstream in{ m_path }; - in >> m_json; + try + { + std::ifstream in{ m_path }; + boost::json::stream_parser parser; + std::string line; + while(std::getline(in, line)) + { + parser.write(line); + } + parser.finish(); + boost::json::value value{ parser.release() }; + if(value.is_object()) + { + m_json = value.as_object(); + } + } + catch(...) { } } } @@ -39,7 +54,7 @@ namespace Nickvision::App bool DataFileBase::save() { std::ofstream out{ m_path }; - out << m_json; + out << m_json << std::endl; m_saved({}); return true; } diff --git a/src/network/web.cpp b/src/network/web.cpp index 76fe0ca..09c5c9c 100644 --- a/src/network/web.cpp +++ b/src/network/web.cpp @@ -15,11 +15,11 @@ namespace Nickvision::Network return curl.perform() == CURLE_OK; } - Json::Value Web::fetchJson(const std::string& url) + boost::json::value Web::fetchJson(const std::string& url) { if(url.empty()) { - return ""; + return {}; } CurlEasy curl{ url }; std::stringstream out; @@ -31,12 +31,7 @@ namespace Nickvision::Network std::string data{ out.str() }; if(!data.empty()) { - Json::Value root; - Json::Reader reader; - if(reader.parse(out.str(), root, false)) - { - return root; - } + return boost::json::parse(data); } } return {}; diff --git a/src/update/updater.cpp b/src/update/updater.cpp index 8ebf747..3c3350e 100644 --- a/src/update/updater.cpp +++ b/src/update/updater.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include "filesystem/userdirectories.h" #include "helpers/stringhelpers.h" #include "network/web.h" @@ -58,26 +58,34 @@ namespace Nickvision::Update Version Updater::fetchCurrentVersion(VersionType versionType) { std::lock_guard lock{ m_mutex }; - Json::Value root{ Web::fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases") }; - if (!root.empty()) + boost::json::value root{ Web::fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases") }; + if (!root.is_array()) { - for (const Json::Value& release : root) + return {}; + } + for (const boost::json::value& release : root.as_array()) + { + if(!release.is_object()) { - std::string version{ release.get("tag_name", "NULL").asString() }; - if (version.empty() || version == "NULL") - { - return {}; - } - if (versionType == VersionType::Stable && version.find('-') == std::string::npos) - { - m_latestStableReleaseId = release.get("id", -1).asInt(); - return version; - } - if (versionType == VersionType::Preview && version.find('-') != std::string::npos) - { - m_latestPreviewReleaseId = release.get("id", -1).asInt(); - return version; - } + continue; + } + boost::json::object releaseObject{ release.as_object() }; + const boost::json::value& tagNameValue{ releaseObject["tag_name"] }; + if (!tagNameValue.is_string()) + { + return {}; + } + std::string version{ tagNameValue.as_string() }; + const boost::json::value& id{ releaseObject["id"] }; + if (versionType == VersionType::Stable && version.find('-') == std::string::npos) + { + m_latestStableReleaseId = id.is_int64() ? static_cast(id.as_int64()) : -1; + return version; + } + if (versionType == VersionType::Preview && version.find('-') != std::string::npos) + { + m_latestPreviewReleaseId = id.is_int64() ? static_cast(id.as_int64()) : -1; + return version; } } return {}; @@ -91,25 +99,43 @@ namespace Nickvision::Update { return false; } - Json::Value root{ Web::fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases/" + std::to_string(versionType == VersionType::Stable ? m_latestStableReleaseId : m_latestPreviewReleaseId)) }; - if (!root.empty()) + boost::json::value root{ Web::fetchJson("https://api.github.com/repos/" + m_repoOwner + "/" + m_repoName + "/releases/" + std::to_string(versionType == VersionType::Stable ? m_latestStableReleaseId : m_latestPreviewReleaseId)) }; + if (!root.is_object()) + { + return false; + } + boost::json::object rootObject{ root.as_object() }; + const boost::json::value& assets{ rootObject["assets"] }; + if (!assets.is_array()) + { + return false; + } + for (const boost::json::value& asset : assets.as_array()) { - for (const Json::Value& asset : root.get("assets", {})) + if (!asset.is_object()) + { + continue; + } + boost::json::object assetObject{ asset.as_object() }; + const boost::json::value& nameValue{ assetObject["name"] }; + if(!nameValue.is_string()) + { + return false; + } + std::string name{ nameValue.as_string() }; + if (StringHelpers::lower(name).find("setup.exe") != std::string::npos) { - std::string name{ asset.get("name", "").asString() }; - if (StringHelpers::lower(name).find("setup.exe") != std::string::npos) + std::filesystem::path setupPath{ UserDirectories::get(UserDirectory::Cache) / name }; + const boost::json::value& urlValue{ assetObject["browser_download_url"] }; + if (urlValue.is_string() && Web::downloadFile(urlValue.as_string().c_str(), setupPath)) { - std::filesystem::path setupPath{ UserDirectories::get(UserDirectory::Cache) / name }; - if (Web::downloadFile(asset.get("browser_download_url", "").asString(), setupPath)) + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (reinterpret_cast(ShellExecuteA(nullptr, "open", setupPath.string().c_str(), nullptr, nullptr, SW_SHOWDEFAULT)) > 32) { - CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - if (reinterpret_cast(ShellExecuteA(nullptr, "open", setupPath.string().c_str(), nullptr, nullptr, SW_SHOWDEFAULT)) > 32) - { - return true; - } + return true; } - return false; } + return false; } } return false; diff --git a/tests/datafiletests.cpp b/tests/datafiletests.cpp index 2acc77f..0d8d90f 100644 --- a/tests/datafiletests.cpp +++ b/tests/datafiletests.cpp @@ -26,7 +26,12 @@ class AppConfig : public DataFileBase Theme getTheme() const { - return static_cast(m_json.get("Theme", (int)Theme::System).asInt()); + const boost::json::value& theme{ m_json["Theme"] }; + if(!theme.is_int64()) + { + return Theme::System; + } + return static_cast(theme.as_int64()); } void setTheme(Theme theme) @@ -34,26 +39,40 @@ class AppConfig : public DataFileBase m_json["Theme"] = static_cast(theme); } - WindowGeometry getWindowGeometry() + WindowGeometry getWindowGeometry() const { WindowGeometry geometry; - const Json::Value json{ m_json["WindowGeometry"] }; - geometry.setWidth(json.get("Width", 800).asInt64()); - geometry.setHeight(json.get("Height", 600).asInt64()); - geometry.setIsMaximized(json.get("IsMaximized", false).asBool()); + if(!m_json["WindowGeometry"].is_object()) + { + geometry.setWidth(800); + geometry.setHeight(600); + geometry.setIsMaximized(false); + return geometry; + } + boost::json::object& obj{ m_json["WindowGeometry"].as_object() }; + geometry.setWidth(obj["Width"].is_int64() ? obj["Width"].as_int64() : 800); + geometry.setHeight(obj["Height"].is_int64() ? obj["Height"].as_int64() : 600); + geometry.setIsMaximized(obj["IsMaximized"].is_bool() ? obj["IsMaximized"].as_bool() : false); return geometry; } void setWindowGeometry(const WindowGeometry& geometry) { - m_json["WindowGeometry"]["Width"] = static_cast(geometry.getWidth()); - m_json["WindowGeometry"]["Height"] = static_cast(geometry.getHeight()); - m_json["WindowGeometry"]["IsMaximized"] = geometry.isMaximized(); + boost::json::object obj; + obj["Width"] = geometry.getWidth(); + obj["Height"] = geometry.getHeight(); + obj["IsMaximized"] = geometry.isMaximized(); + m_json["WindowGeometry"] = obj; } bool getAutomaticallyCheckForUpdates() const { - return m_json.get("AutomaticallyCheckForUpdates", true).asBool(); + const boost::json::value& value{ m_json["AutomaticallyCheckForUpdates"] }; + if(!value.is_bool()) + { + return true; + } + return value.as_bool(); } void setAutomaticallyCheckForUpdates(bool value) diff --git a/tests/webtests.cpp b/tests/webtests.cpp index 6989ee2..1a2fc9d 100644 --- a/tests/webtests.cpp +++ b/tests/webtests.cpp @@ -22,6 +22,7 @@ TEST(WebTests, DownloadFile1) TEST(WebTests, FetchJsonString1) { - Json::Value json{ Web::fetchJson("https://api.github.com/repos/nickvisionapps/denaro/tags") }; - ASSERT_FALSE(json.empty()); + boost::json::value json{ Web::fetchJson("https://api.github.com/repos/nickvisionapps/denaro/tags") }; + ASSERT_FALSE(json.is_null()); + ASSERT_TRUE(json.is_array()); } \ No newline at end of file