From d11c37a0b4eddb75ea4f200a41968f5991e4b5a1 Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Thu, 18 Sep 2025 21:58:33 -0400 Subject: [PATCH 1/7] fix: Database encryption --- CHANGELOG.md | 9 +++++++++ CMakeLists.txt | 2 +- Doxyfile | 2 +- include/database/sqlitedatabase.h | 2 +- manual/README.md | 6 +++--- src/database/sqlitedatabase.cpp | 30 +++++++++++++++++++++++++----- 6 files changed, 40 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244edc0..a509e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2025.9.3 +### Breaking Changes +None +### New APIs +None +### Fixes +#### Database +- Fixed an issue where the sqlite database did not encrypt and decrypt correctly + ## 2025.9.2 ### Breaking Changes None diff --git a/CMakeLists.txt b/CMakeLists.txt index 387d2b4..5b76f75 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 2025.9.2 DESCRIPTION "A cross-platform base for native Nickvision applications.") +project ("libnick" LANGUAGES C CXX VERSION 2025.9.3 DESCRIPTION "A cross-platform base for native Nickvision applications.") include(CMakePackageConfigHelpers) include(GNUInstallDirs) include(CTest) diff --git a/Doxyfile b/Doxyfile index c8ed9ba..19089a2 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 = "2025.9.2" +PROJECT_NUMBER = "2025.9.3" # 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/include/database/sqlitedatabase.h b/include/database/sqlitedatabase.h index 56c436c..d854330 100644 --- a/include/database/sqlitedatabase.h +++ b/include/database/sqlitedatabase.h @@ -141,4 +141,4 @@ namespace Nickvision::Database }; } -#endif //SQLDATABASE_H \ No newline at end of file +#endif //SQLDATABASE_H diff --git a/manual/README.md b/manual/README.md index a359c62..a3eab18 100644 --- a/manual/README.md +++ b/manual/README.md @@ -6,14 +6,14 @@ 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. -## 2025.9.2 +## 2025.9.3 ### Breaking Changes None ### New APIs None ### Fixes -#### Helpers -- Fixed a deadlock in `JsonFileBase` +#### Database +- Fixed an issue where the sqlite database did not encrypt and decrypt correctly ## Dependencies The following are a list of dependencies used by libnick. diff --git a/src/database/sqlitedatabase.cpp b/src/database/sqlitedatabase.cpp index e0926a4..ab898ee 100644 --- a/src/database/sqlitedatabase.cpp +++ b/src/database/sqlitedatabase.cpp @@ -14,7 +14,7 @@ namespace Nickvision::Database { throw std::runtime_error("Unable to open sql database."); } - if(sqlite3_exec(m_database, "PRAGMA schema_version", nullptr, nullptr, nullptr) != SQLITE_OK) + if(sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) != SQLITE_OK) { m_isEncrypted = true; m_isUnlocked = false; @@ -82,7 +82,7 @@ namespace Nickvision::Database } else { - m_isUnlocked = sqlite3_exec(m_database, "PRAGMA schema_version", nullptr, nullptr, nullptr) == SQLITE_OK; + m_isUnlocked = sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) == SQLITE_OK; } return m_isUnlocked; } @@ -101,9 +101,29 @@ namespace Nickvision::Database { return true; } + //If empty database, can use sqlite3_key + SqliteStatement statement{ m_database, "SELECT count(*) FROM sqlite_master;" }; + int tableCount{ -1 }; + if(statement.step() == SqliteStepResult::Row) + { + tableCount = statement.getColumn(0); + } + if(tableCount == 0) + { + if(sqlite3_key(m_database, password.c_str(), static_cast(password.size())) != SQLITE_OK) + { + return false; + } + else + { + m_isUnlocked = sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) == SQLITE_OK; + m_isEncrypted = true; + return true; + } + } //Create temp encrypted database std::filesystem::path tempPath{ (m_path.string() + ".encrypt") }; - std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS encrypted KEY '" + password + "'" }; + std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS encrypted KEY '" + password + "'" }; sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr); sqlite3_exec(m_database, "SELECT sqlcipher_export('encrypted')", nullptr, nullptr, nullptr); sqlite3_exec(m_database, "DETACH DATABASE encrypted", nullptr, nullptr, nullptr); @@ -120,7 +140,7 @@ namespace Nickvision::Database { throw std::runtime_error("Unable to open sql database with password."); } - m_isUnlocked = sqlite3_exec(m_database, "PRAGMA schema_version", nullptr, nullptr, nullptr) == SQLITE_OK; + m_isUnlocked = sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) == SQLITE_OK; m_isEncrypted = true; return true; } @@ -202,4 +222,4 @@ namespace Nickvision::Database } return *this; } -} \ No newline at end of file +} From f17f095aa8becd4af79c54d0e8d8f9c1a9590224 Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 15:17:09 -0400 Subject: [PATCH 2/7] feat: Update vcpkg --- .github/workflows/linux.yml | 4 ++-- .github/workflows/macos.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7d61e47..a0250d0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -51,9 +51,9 @@ jobs: with: pkgs: boost-json cpr gettext-libintl glib gtest libsecret maddy triplet: ${{ matrix.variant.triplet }} - revision: b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 + revision: 29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c token: ${{ github.token }} - cache-key: ${{ matrix.variant.triplet }}-b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 + cache-key: ${{ matrix.variant.triplet }}-29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c - name: "Build" working-directory: ${{github.workspace}}/build run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index b028f54..28d956e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -31,9 +31,9 @@ jobs: with: pkgs: boost-json cpr gettext-libintl glib gtest maddy triplet: arm64-osx - revision: b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 + revision: 29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c token: ${{ github.token }} - cache-key: "arm64-osx-b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01" + cache-key: "arm64-osx-29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c" - name: "Build" working-directory: ${{github.workspace}}/build run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 8caaf21..071e962 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -38,9 +38,9 @@ jobs: with: pkgs: boost-json cpr gettext-libintl gtest maddy sqlcipher triplet: ${{ matrix.variant.triplet }} - revision: b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 + revision: 29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c token: ${{ github.token }} - cache-key: ${{ matrix.variant.triplet }}-b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01 + cache-key: ${{ matrix.variant.triplet }}-29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c - name: "Build" working-directory: ${{github.workspace}}/build run: | From 0e600d0112de6f8c6848163b7b6febbc821f579a Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 18:20:18 -0400 Subject: [PATCH 3/7] feat: Better error handling --- src/database/sqlitedatabase.cpp | 42 ++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/database/sqlitedatabase.cpp b/src/database/sqlitedatabase.cpp index ab898ee..caecf9b 100644 --- a/src/database/sqlitedatabase.cpp +++ b/src/database/sqlitedatabase.cpp @@ -10,7 +10,7 @@ namespace Nickvision::Database m_isUnlocked{ true }, m_database{ nullptr } { - if (sqlite3_open_v2(m_path.string().c_str(), &m_database, m_flags, nullptr) != SQLITE_OK) + if(sqlite3_open_v2(m_path.string().c_str(), &m_database, m_flags, nullptr) != SQLITE_OK) { throw std::runtime_error("Unable to open sql database."); } @@ -124,11 +124,23 @@ namespace Nickvision::Database //Create temp encrypted database std::filesystem::path tempPath{ (m_path.string() + ".encrypt") }; std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS encrypted KEY '" + password + "'" }; - sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr); - sqlite3_exec(m_database, "SELECT sqlcipher_export('encrypted')", nullptr, nullptr, nullptr); - sqlite3_exec(m_database, "DETACH DATABASE encrypted", nullptr, nullptr, nullptr); + if(sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to attach temporary database."); + } + if(sqlite3_exec(m_database, "SELECT sqlcipher_export('encrypted')", nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to encrypt temporary database."); + } + if(sqlite3_exec(m_database, "DETACH DATABASE encrypted", nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to detach temporary database."); + } //Remove old encrypted database - sqlite3_close(m_database); + if(sqlite3_close(m_database) != SQLITE_OK) + { + throw std::runtime_error("Unable to close old sql databse."); + } std::filesystem::remove(m_path); std::filesystem::rename(tempPath, m_path); //Open new encrypted database @@ -155,11 +167,23 @@ namespace Nickvision::Database //Create temporary decrypted database std::filesystem::path tempPath{ (m_path.string() + ".decrypt") }; std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS plaintext KEY ''" }; - sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr); - sqlite3_exec(m_database, "SELECT sqlcipher_export('plaintext')", nullptr, nullptr, nullptr); - sqlite3_exec(m_database, "DETACH DATABASE plaintext", nullptr, nullptr, nullptr); + if(sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to attach temporary database."); + } + if(sqlite3_exec(m_database, "SELECT sqlcipher_export('plaintext')", nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to decrypt temporary database."); + } + if(sqlite3_exec(m_database, "DETACH DATABASE plaintext", nullptr, nullptr, nullptr) != SQLITE_OK) + { + throw std::runtime_error("Unable to detach temporary database."); + } //Remove old encrypted database - sqlite3_close(m_database); + if(sqlite3_close(m_database) != SQLITE_OK) + { + throw std::runtime_error("Unable to close old sql databse."); + } std::filesystem::remove(m_path); std::filesystem::rename(tempPath, m_path); //Open new decrypted database From 32253cc38060937386aba0ba23f5fbedfa24a00e Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 19:38:00 -0400 Subject: [PATCH 4/7] fix: Windows crash --- CHANGELOG.md | 10 +++++ CMakeLists.txt | 2 +- Doxyfile | 2 +- include/database/sqlitestatement.h | 4 ++ manual/README.md | 7 ++-- src/database/sqlitedatabase.cpp | 62 +++++++++++------------------- src/database/sqlitestatement.cpp | 9 +++++ 7 files changed, 52 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a509e16..b87345e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 2025.9.4 +### Breaking Changes +None +### New APIs +#### Database +- Added `reset` method to `SqliteStatement` +### Fixes +#### Database +- Fixed an issue where the sqlite database crashed on Windows + ## 2025.9.3 ### Breaking Changes None diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b76f75..9b38ef7 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 2025.9.3 DESCRIPTION "A cross-platform base for native Nickvision applications.") +project ("libnick" LANGUAGES C CXX VERSION 2025.9.4 DESCRIPTION "A cross-platform base for native Nickvision applications.") include(CMakePackageConfigHelpers) include(GNUInstallDirs) include(CTest) diff --git a/Doxyfile b/Doxyfile index 19089a2..d134d6d 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 = "2025.9.3" +PROJECT_NUMBER = "2025.9.4" # 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/include/database/sqlitestatement.h b/include/database/sqlitestatement.h index 8cb1234..78b2ed9 100644 --- a/include/database/sqlitestatement.h +++ b/include/database/sqlitestatement.h @@ -56,6 +56,10 @@ namespace Nickvision::Database * @returns SqliteStepResult */ SqliteStepResult step() noexcept; + /** + * @brief Resets the statement to its initial state, clearing any bindings as well. + */ + void reset() noexcept; /** * @brief Binds a value to a sqlite parameter. * @tparam T The type of the value to bind diff --git a/manual/README.md b/manual/README.md index a3eab18..9f38726 100644 --- a/manual/README.md +++ b/manual/README.md @@ -6,14 +6,15 @@ 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. -## 2025.9.3 +## 2025.9.4 ### Breaking Changes None ### New APIs -None +#### Database +- Added `reset` method to `SqliteStatement` ### Fixes #### Database -- Fixed an issue where the sqlite database did not encrypt and decrypt correctly +- Fixed an issue where the sqlite database crashed on Windows ## Dependencies The following are a list of dependencies used by libnick. diff --git a/src/database/sqlitedatabase.cpp b/src/database/sqlitedatabase.cpp index caecf9b..18fdf67 100644 --- a/src/database/sqlitedatabase.cpp +++ b/src/database/sqlitedatabase.cpp @@ -102,40 +102,33 @@ namespace Nickvision::Database return true; } //If empty database, can use sqlite3_key - SqliteStatement statement{ m_database, "SELECT count(*) FROM sqlite_master;" }; - int tableCount{ -1 }; - if(statement.step() == SqliteStepResult::Row) { - tableCount = statement.getColumn(0); - } - if(tableCount == 0) - { - if(sqlite3_key(m_database, password.c_str(), static_cast(password.size())) != SQLITE_OK) + SqliteStatement statement{ m_database, "SELECT count(*) FROM sqlite_master;" }; + int tableCount{ -1 }; + if(statement.step() == SqliteStepResult::Row) { - return false; + tableCount = statement.getColumn(0); } - else + if(tableCount == 0) { - m_isUnlocked = sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) == SQLITE_OK; - m_isEncrypted = true; - return true; + if(sqlite3_key(m_database, password.c_str(), static_cast(password.size())) != SQLITE_OK) + { + return false; + } + else + { + m_isUnlocked = sqlite3_exec(m_database, "SELECT count(*) FROM sqlite_master;", nullptr, nullptr, nullptr) == SQLITE_OK; + m_isEncrypted = true; + return true; + } } } //Create temp encrypted database std::filesystem::path tempPath{ (m_path.string() + ".encrypt") }; - std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS encrypted KEY '" + password + "'" }; - if(sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to attach temporary database."); - } - if(sqlite3_exec(m_database, "SELECT sqlcipher_export('encrypted')", nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to encrypt temporary database."); - } - if(sqlite3_exec(m_database, "DETACH DATABASE encrypted", nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to detach temporary database."); - } + std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS encrypted KEY '" + password + "';" }; + sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr); + sqlite3_exec(m_database, "SELECT sqlcipher_export('encrypted');", nullptr, nullptr, nullptr); + sqlite3_exec(m_database, "DETACH DATABASE encrypted;", nullptr, nullptr, nullptr); //Remove old encrypted database if(sqlite3_close(m_database) != SQLITE_OK) { @@ -166,19 +159,10 @@ namespace Nickvision::Database { //Create temporary decrypted database std::filesystem::path tempPath{ (m_path.string() + ".decrypt") }; - std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS plaintext KEY ''" }; - if(sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to attach temporary database."); - } - if(sqlite3_exec(m_database, "SELECT sqlcipher_export('plaintext')", nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to decrypt temporary database."); - } - if(sqlite3_exec(m_database, "DETACH DATABASE plaintext", nullptr, nullptr, nullptr) != SQLITE_OK) - { - throw std::runtime_error("Unable to detach temporary database."); - } + std::string cmd{ "ATTACH DATABASE '" + tempPath.string() + "' AS plaintext KEY '';" }; + sqlite3_exec(m_database, cmd.c_str(), nullptr, nullptr, nullptr); + sqlite3_exec(m_database, "SELECT sqlcipher_export('plaintext');", nullptr, nullptr, nullptr); + sqlite3_exec(m_database, "DETACH DATABASE plaintext;", nullptr, nullptr, nullptr); //Remove old encrypted database if(sqlite3_close(m_database) != SQLITE_OK) { diff --git a/src/database/sqlitestatement.cpp b/src/database/sqlitestatement.cpp index 28ef474..3663a94 100644 --- a/src/database/sqlitestatement.cpp +++ b/src/database/sqlitestatement.cpp @@ -44,6 +44,15 @@ namespace Nickvision::Database return SqliteStepResult::Error; } + void SqliteStatement::reset() noexcept + { + if(m_statement) + { + sqlite3_reset(m_statement); + sqlite3_clear_bindings(m_statement); + } + } + SqliteStatement& SqliteStatement::operator=(SqliteStatement&& other) noexcept { if(this != &other) From a78cab44523333b11b5700d02fed12bb56271c10 Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 19:48:39 -0400 Subject: [PATCH 5/7] fix: Spelling --- src/database/sqlitedatabase.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/sqlitedatabase.cpp b/src/database/sqlitedatabase.cpp index 18fdf67..3e262e1 100644 --- a/src/database/sqlitedatabase.cpp +++ b/src/database/sqlitedatabase.cpp @@ -132,7 +132,7 @@ namespace Nickvision::Database //Remove old encrypted database if(sqlite3_close(m_database) != SQLITE_OK) { - throw std::runtime_error("Unable to close old sql databse."); + throw std::runtime_error("Unable to close old sql database."); } std::filesystem::remove(m_path); std::filesystem::rename(tempPath, m_path); @@ -166,7 +166,7 @@ namespace Nickvision::Database //Remove old encrypted database if(sqlite3_close(m_database) != SQLITE_OK) { - throw std::runtime_error("Unable to close old sql databse."); + throw std::runtime_error("Unable to close old sql database."); } std::filesystem::remove(m_path); std::filesystem::rename(tempPath, m_path); From 5f5f7e5fa1fe40f88ea7245ebcd378901bc3b4b0 Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 20:53:29 -0400 Subject: [PATCH 6/7] fix: Version handling --- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 4 +-- src/update/version.cpp | 55 +++++++++++++++++++---------------- tests/versiontests.cpp | 38 +++++++++++++++++++++++- 5 files changed, 71 insertions(+), 30 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a0250d0..b7c8822 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -11,7 +11,7 @@ permissions: contents: read env: GITHUB_ACTIONS: true - VCPKG_ROOT: ${{github.workspace}}/vcpkg + VCPKG_ROOT: ${{ github.workspace }}/vcpkg jobs: build: name: "Build on Linux" diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 28d956e..777c4cd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -11,7 +11,7 @@ permissions: contents: read env: GITHUB_ACTIONS: true - VCPKG_ROOT: ${{github.workspace}}/vcpkg + VCPKG_ROOT: ${{ github.workspace }}/vcpkg jobs: build: name: "Build on macOS" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 071e962..48cc32c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -11,7 +11,7 @@ permissions: contents: read env: GITHUB_ACTIONS: true - VCPKG_ROOT: ${{github.workspace}}/vcpkg + VCPKG_ROOT: ${{ github.workspace }}/vcpkg jobs: build: name: "Build on Windows" @@ -19,7 +19,7 @@ jobs: matrix: variant: - arch: x64 - runner: windows-latest + runner: windows-2025 triplet: x64-windows #- arch: arm64 # runner: windows-11-arm diff --git a/src/update/version.cpp b/src/update/version.cpp index c7c7718..81a75f0 100644 --- a/src/update/version.cpp +++ b/src/update/version.cpp @@ -1,6 +1,8 @@ #include "update/version.h" #include -#include +#include "helpers/stringhelpers.h" + +using namespace Nickvision::Helpers; namespace Nickvision::Update { @@ -40,42 +42,45 @@ namespace Nickvision::Update m_minor{ 0 }, m_build{ 0 } { - std::string s{ version }; - size_t pos{ 0 }; - int i{ 0 }; - while ((pos = s.find('.')) != std::string::npos) + + std::vector splits{ StringHelpers::split(version, ".", false) }; + if(splits.size() < 3) { - std::string token{ s.substr(0, pos) }; - if (i == 0) - { - m_major = std::stoi(token); - } - else if (i == 1) + throw std::invalid_argument("Ill-formated version string."); + } + m_major = std::stoi(splits[0]); + m_minor = std::stoi(splits[1]); + if(splits.size() == 3) + { + if(splits[2].find("-") == std::string::npos) { - m_minor = std::stoi(token); + m_build = std::stoi(splits[2]); } else { - throw std::invalid_argument("Ill-formated version string."); + std::vector devSplits{ StringHelpers::split(splits[2], "-", false) }; + if(devSplits.size() != 2) + { + throw std::invalid_argument("Ill-formated version string."); + } + m_build = std::stoi(devSplits[0]); + m_dev = devSplits[1]; } - s.erase(0, pos + 1); - i++; } - if (i != 2) + else if(splits.size() == 4) { - throw std::invalid_argument("Ill-formated version string."); + m_build = std::stoi(splits[2]); + m_dev = splits[3]; } - size_t dashIndex{ s.find('-') }; - if(dashIndex == std::string::npos) + else { - dashIndex = s.find('.'); + throw std::invalid_argument("Ill-formated version string."); } - s.erase(0, dashIndex); - if (!s.empty() && (s[0] == '-' || s[0] == '.')) //dev version + m_str = std::to_string(m_major) + "." + std::to_string(m_minor) + "." + std::to_string(m_build); + if(!m_dev.empty()) { - m_dev = s; + m_str += "-" + m_dev; } - m_str = std::to_string(m_major) + "." + std::to_string(m_minor) + "." + std::to_string(m_build) + m_dev; } int Version::getMajor() const noexcept @@ -224,4 +229,4 @@ namespace Nickvision::Update { return !(operator==(compare)); } -} \ No newline at end of file +} diff --git a/tests/versiontests.cpp b/tests/versiontests.cpp index fe4cf02..f45514e 100644 --- a/tests/versiontests.cpp +++ b/tests/versiontests.cpp @@ -9,10 +9,46 @@ TEST(VersionTests, EmptyVersion) ASSERT_EQ(v.empty(), true); } +TEST(VersionTests, StableVersion1) +{ + Version v{ "2025.9.0" }; + ASSERT_EQ(v.getVersionType(), VersionType::Stable); + ASSERT_EQ(v.str(), "2025.9.0"); +} + +TEST(VersionTests, StableVersion2) +{ + Version v{ "0.2.0" }; + ASSERT_EQ(v.getVersionType(), VersionType::Stable); + ASSERT_EQ(v.str(), "0.2.0"); +} + +TEST(VersionTests, StableVersion3) +{ + Version v{ "1.2.4" }; + ASSERT_EQ(v.getVersionType(), VersionType::Stable); + ASSERT_EQ(v.str(), "1.2.4"); +} + TEST(VersionTests, BetaVersion1) { Version v{ "2010.5.0-beta1" }; ASSERT_EQ(v.getVersionType(), VersionType::Preview); + ASSERT_EQ(v.str(), "2010.5.0-beta1"); +} + +TEST(VersionTests, BetaVersion2) +{ + Version v{ "2025.9.1-next" }; + ASSERT_EQ(v.getVersionType(), VersionType::Preview); + ASSERT_EQ(v.str(), "2025.9.1-next"); +} + +TEST(VersionTests, BetaVersion3) +{ + Version v{ "2025.9.1.beta1" }; + ASSERT_EQ(v.getVersionType(), VersionType::Preview); + ASSERT_EQ(v.str(), "2025.9.1-beta1"); } TEST(VersionTests, BadVersion1) @@ -64,4 +100,4 @@ TEST(VersionTests, CompareVersion4) ASSERT_FALSE(v1 > v2); ASSERT_FALSE(v2 < v1); ASSERT_TRUE(v1 == v2); -} \ No newline at end of file +} From 5e0050fd5e47884b811b84af95c09c5f275f57fd Mon Sep 17 00:00:00 2001 From: Nick Logozzo Date: Sun, 21 Sep 2025 21:20:55 -0400 Subject: [PATCH 7/7] fix: Version parsing --- CHANGELOG.md | 2 ++ include/update/updater.h | 1 + manual/README.md | 2 ++ src/update/updater.cpp | 40 ++++++++++++++++++++++------------------ src/update/version.cpp | 8 ++++++++ tests/versiontests.cpp | 19 ++++++++++++++----- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b87345e..d24aef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ None ### Fixes #### Database - Fixed an issue where the sqlite database crashed on Windows +#### Update +- Fixed parsing of development versions ## 2025.9.3 ### Breaking Changes diff --git a/include/update/updater.h b/include/update/updater.h index 67a1cff..f6561d7 100644 --- a/include/update/updater.h +++ b/include/update/updater.h @@ -56,6 +56,7 @@ namespace Nickvision::Update /** * @brief Gets the latest version of the provided type from the GitHub repo. * @brief This method looks for tags in the format major.minor.build-dev or major.minor.build.dev for preview versions and major.minor.build for stable versions. + * @brief If the VersionType is Preview, the updater may return a stable version if it is greater than the latest preview. * @param versionType The type of the version to get * @return The current version of the provided type if available, else empty Version */ diff --git a/manual/README.md b/manual/README.md index 9f38726..42e8307 100644 --- a/manual/README.md +++ b/manual/README.md @@ -15,6 +15,8 @@ None ### Fixes #### Database - Fixed an issue where the sqlite database crashed on Windows +#### Update +- Fixed parsing of development versions ## Dependencies The following are a list of dependencies used by libnick. diff --git a/src/update/updater.cpp b/src/update/updater.cpp index dd659ae..cd083ae 100644 --- a/src/update/updater.cpp +++ b/src/update/updater.cpp @@ -63,8 +63,8 @@ namespace Nickvision::Update { return {}; } - std::string stableVersion; - std::string previewVersion; + Version stable; + Version preview; for (const boost::json::value& release : root.as_array()) { if(!release.is_object()) @@ -72,38 +72,42 @@ namespace Nickvision::Update continue; } boost::json::object releaseObject = release.as_object(); - const boost::json::value& tagNameValue{ releaseObject["tag_name"] }; - if (!tagNameValue.is_string()) + const boost::json::value& id{ releaseObject["id"] }; + const boost::json::value& tagName{ releaseObject["tag_name"] }; + const boost::json::value& prerelease{ releaseObject["prerelease"] }; + if (!id.is_int64() || !tagName.is_string() || !prerelease.is_bool()) { - return {}; + continue; } - std::string version{ tagNameValue.as_string() }; - size_t splitCount{ StringHelpers::split(version, ".", false).size() }; - const boost::json::value& id{ releaseObject["id"] }; - if (stableVersion.empty() && versionType == VersionType::Stable && version.find('-') == std::string::npos && splitCount == 3) + if (prerelease.as_bool() && preview.empty()) { - m_latestStableReleaseId = id.is_int64() ? static_cast(id.as_int64()) : -1; - stableVersion = version; + preview = Version(tagName.as_string().c_str()); + m_latestPreviewReleaseId = id.as_int64(); } - if (previewVersion.empty() && versionType == VersionType::Preview && (version.find('-') != std::string::npos || splitCount == 4)) + else if(stable.empty()) { - m_latestPreviewReleaseId = id.is_int64() ? static_cast(id.as_int64()) : -1; - previewVersion = version; + stable = Version(tagName.as_string().c_str()); + m_latestStableReleaseId = id.as_int64(); } - if (!stableVersion.empty() && !previewVersion.empty()) + if(!stable.empty() && !preview.empty()) { break; } } + if(stable > preview) + { + preview = stable; + m_latestPreviewReleaseId = m_latestStableReleaseId; + } if(versionType == VersionType::Stable) { - return stableVersion.empty() ? Version() : Version(stableVersion); + return stable; } else if(versionType == VersionType::Preview) { - return previewVersion.empty() ? Version() : Version(previewVersion); + return preview; } - return Version(); + return {}; } bool Updater::downloadUpdate(VersionType versionType, const std::filesystem::path& path, const std::string& assetName, bool exactMatch, const cpr::ProgressCallback& progress) noexcept diff --git a/src/update/version.cpp b/src/update/version.cpp index 81a75f0..694f565 100644 --- a/src/update/version.cpp +++ b/src/update/version.cpp @@ -158,6 +158,10 @@ namespace Nickvision::Update { return true; } + else + { + return m_dev < compare.m_dev; + } } } } @@ -209,6 +213,10 @@ namespace Nickvision::Update { return false; } + else + { + return m_dev > compare.m_dev; + } } } } diff --git a/tests/versiontests.cpp b/tests/versiontests.cpp index f45514e..bedeaf1 100644 --- a/tests/versiontests.cpp +++ b/tests/versiontests.cpp @@ -95,9 +95,18 @@ TEST(VersionTests, CompareVersion3) TEST(VersionTests, CompareVersion4) { - Version v1{ "2024.6.0" }; - Version v2{ "2024.6.0" }; - ASSERT_FALSE(v1 > v2); - ASSERT_FALSE(v2 < v1); - ASSERT_TRUE(v1 == v2); + Version v1{ "2025.9.0-beta1" }; + Version v2{ "2025.8.0" }; + ASSERT_TRUE(v1 > v2); + ASSERT_TRUE(v2 < v1); + ASSERT_FALSE(v1 == v2); +} + +TEST(VersionTests, CompareVersion5) +{ + Version v1{ "2025.9.0-beta2" }; + Version v2{ "2025.9.0-beta1" }; + ASSERT_TRUE(v1 > v2); + ASSERT_TRUE(v2 < v1); + ASSERT_FALSE(v1 == v2); }