Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ permissions:
contents: read
env:
GITHUB_ACTIONS: true
VCPKG_ROOT: ${{github.workspace}}/vcpkg
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
jobs:
build:
name: "Build on Windows"
strategy:
matrix:
variant:
- arch: x64
runner: windows-latest
runner: windows-2025
triplet: x64-windows
#- arch: arm64
# runner: windows-11-arm
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# 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
#### Update
- Fixed parsing of development versions

## 2025.9.3
### Breaking Changes
None
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions include/database/sqlitestatement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/update/updater.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
9 changes: 6 additions & 3 deletions manual/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

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
#### Update
- Fixed parsing of development versions

## Dependencies
The following are a list of dependencies used by libnick.
Expand Down
52 changes: 30 additions & 22 deletions src/database/sqlitedatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand Down Expand Up @@ -102,33 +102,38 @@ 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<int>(0);
}
if(tableCount == 0)
{
if(sqlite3_key(m_database, password.c_str(), static_cast<int>(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<int>(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<int>(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);
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
sqlite3_close(m_database);
if(sqlite3_close(m_database) != SQLITE_OK)
{
throw std::runtime_error("Unable to close old sql database.");
}
std::filesystem::remove(m_path);
std::filesystem::rename(tempPath, m_path);
//Open new encrypted database
Expand All @@ -154,12 +159,15 @@ 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 ''" };
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);
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
sqlite3_close(m_database);
if(sqlite3_close(m_database) != SQLITE_OK)
{
throw std::runtime_error("Unable to close old sql database.");
}
std::filesystem::remove(m_path);
std::filesystem::rename(tempPath, m_path);
//Open new decrypted database
Expand Down
9 changes: 9 additions & 0 deletions src/database/sqlitestatement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 22 additions & 18 deletions src/update/updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,47 +63,51 @@ 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())
{
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<int>(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<int>(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
Expand Down
63 changes: 38 additions & 25 deletions src/update/version.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "update/version.h"
#include <stdexcept>
#include <iostream>
#include "helpers/stringhelpers.h"

using namespace Nickvision::Helpers;

namespace Nickvision::Update
{
Expand Down Expand Up @@ -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<std::string> 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<std::string> 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
Expand Down Expand Up @@ -153,6 +158,10 @@ namespace Nickvision::Update
{
return true;
}
else
{
return m_dev < compare.m_dev;
}
}
}
}
Expand Down Expand Up @@ -204,6 +213,10 @@ namespace Nickvision::Update
{
return false;
}
else
{
return m_dev > compare.m_dev;
}
}
}
}
Expand All @@ -224,4 +237,4 @@ namespace Nickvision::Update
{
return !(operator==(compare));
}
}
}
Loading