port from mysql conenctor to QTSQL module

add new classes to manage connections
begin work on generic database for client and db
This commit is contained in:
T1ti
2025-09-19 03:44:34 +02:00
parent 33e3fc355a
commit f66c525f90
18 changed files with 1184 additions and 301 deletions

24
.gitignore vendored
View File

@@ -1,18 +1,6 @@
/**
!/bin/**
!/cmake/**
!/etc/**
!/include/**
!/media/**
!/resources/**
!/sql/**
!/src/**
!/test/**
!/.idea/**
!.gitignore
!.CMakeLists.txt
!COPYING
!Doxyfile
!README.md
!todo_1.4.md
!uid_fix_concept.md
/out
/bin
/build
/.vs
/CMakePresets.json
/CMakeUserPresets.json

View File

@@ -88,7 +88,6 @@ SET(LIBARY_OUTPUT_PATH bin)
SET(EXTERNAL_SOURCE_DIR src/external)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# OPTION(USE_SQL "Enable sql uid save ? (require mysql installed)" OFF)
OPTION(VALIDATE_OPENGL_PROGRAMS "Validate Opengl programs" OFF)
IF(VALIDATE_OPENGL_PROGRAMS)
@@ -115,32 +114,7 @@ FetchContent_Declare(
FetchContent_MakeAvailable(FastNoise2)
FIND_PACKAGE(Sol2 REQUIRED)
FIND_PACKAGE(Qt5 COMPONENTS Widgets OpenGLExtensions Gui Network Xml Multimedia REQUIRED)
# IF(USE_SQL)
FIND_LIBRARY(MYSQL_LIBRARY NAMES libmysql
HINTS "${CMAKE_SOURCE_DIR}/../Noggit3libs/mysql")
FIND_LIBRARY(MYSQLCPPCONN_LIBRARY NAMES mysqlcppconn
HINTS "${CMAKE_SOURCE_DIR}/../Noggit3libs/mysql/connector")
FIND_PATH(MYSQLCPPCONN_INCLUDE NAMES cppconn/driver.h
HINTS "${CMAKE_SOURCE_DIR}/../Noggit3libs/mysql/connector")
# Ensure we always include the parent of 'cppconn' folder
if(EXISTS "${MYSQLCPPCONN_INCLUDE}/cppconn")
set(MYSQLCPPCONN_INCLUDE "${MYSQLCPPCONN_INCLUDE}")
elseif(EXISTS "${MYSQLCPPCONN_INCLUDE}/driver.h")
get_filename_component(MYSQLCPPCONN_INCLUDE "${MYSQLCPPCONN_INCLUDE}" DIRECTORY)
endif()
IF(MYSQL_LIBRARY AND MYSQLCPPCONN_LIBRARY AND MYSQLCPPCONN_INCLUDE)
ADD_DEFINITIONS(-DUSE_MYSQL_UID_STORAGE)
SET (mysql_sources src/mysql/mysql.cpp)
SET (mysql_headers src/mysql/mysql.h)
SOURCE_GROUP("mysql" FILES ${mysql_sources} ${mysql_headers})
ELSE()
MESSAGE(FATAL_ERROR "MySQL lib or connector not found")
ENDIF()
# ENDIF()
FIND_PACKAGE(Qt5 COMPONENTS Widgets OpenGLExtensions Gui Network Xml Multimedia Sql REQUIRED)
ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/qt-color-widgets")
ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/framelesshelper")
@@ -297,7 +271,6 @@ ADD_EXECUTABLE(noggit
${opengl_sources}
${math_sources}
${external_sources}
${mysql_sources}
${os_sources}
${util_sources}
${util_headers}
@@ -311,7 +284,6 @@ ADD_EXECUTABLE(noggit
${opengl_headers}
${math_headers}
${external_headers}
${mysql_headers}
${os_headers}
${png_blp_headers}
${ResFiles}
@@ -355,6 +327,7 @@ TARGET_LINK_LIBRARIES (noggit
Qt5::Xml
Qt5::Network
Qt5::Multimedia
Qt5::Sql
ColorWidgets-qt5
FramelessHelper
qt_imgui_widgets
@@ -371,6 +344,78 @@ TARGET_LINK_LIBRARIES (noggit
blizzard-database-library
rapidfuzz::rapidfuzz
)
include(ExternalProject)
# Fetch qsqlmysql.dll plugin based on Qt version #####
execute_process(
COMMAND ${Qt5Core_DIR}/../../../bin/qmake -query QT_VERSION
OUTPUT_VARIABLE QT_FULL_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "Qt full version: ${QT_FULL_VERSION}")
# prebuilds available at : https://github.com/thecodemonkey86/qt_mysql_driver/releases
# get direct download links
if(WIN32)
if(MSVC)
set(QMYSQL_PREBUILT_DRIVER_URL
"https://github.com/thecodemonkey86/qt_mysql_driver/files/5575770/qsqlmysql.dll_Qt_SQL_driver_${QT_FULL_VERSION}_MSVC2019_64-bit.zip"
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # MinGW
set(QMYSQL_PREBUILT_DRIVER_URL
"https://github.com/thecodemonkey86/qt_mysql_driver/files/5575769/qsqlmysql.dll_Qt_SQL_driver_${QT_FULL_VERSION}_MinGW_8.1.0_64-bit.zip"
)
else()
message(WARNING "Unsupported Windows compiler for prebuilt MySQL Qt driver")
endif()
elseif(UNIX)
set(QMYSQL_PREBUILT_DRIVER_URL
"https://github.com/thecodemonkey86/qt_mysql_driver/files/6388052/libqsqlmysql.so_Qt_SQL_driver_${QT_FULL_VERSION}_gcc64.zip"
)
else()
message(WARNING "Unsupported platform for prebuilt MySQL Qt driver")
endif()
set(QMYSQL_PREBUILT_DIR "${CMAKE_BINARY_DIR}/_deps/qt_mysql_driver")
set(QMYSQL_EXTRACT_DIR "${QMYSQL_PREBUILT_DIR}/sqldrivers")
set(QMYSQL_ZIP "${QMYSQL_PREBUILT_DIR}/qsqlmysql_prebuilt.zip")
# Download zip only if not already present
if(NOT EXISTS "${QMYSQL_ZIP}")
message(STATUS "qt MySQL DRIVER zip not found. Downloading qmysql from : ${QMYSQL_PREBUILT_DRIVER_URL}")
file(DOWNLOAD
${QMYSQL_PREBUILT_DRIVER_URL}
${QMYSQL_ZIP}
# SHOW_PROGRESS
STATUS DOWNLOAD_STATUS
TLS_VERIFY ON
)
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
if(NOT STATUS_CODE EQUAL 0)
message(WARNING "Failed to download prebuilt qt MySQL driver: ${DOWNLOAD_STATUS}")
message(WARNING "You can manually download it from https://github.com/thecodemonkey86/qt_mysql_driver/releases or build qsqlmysql.dll yourself from QT source.")
endif()
else()
message(STATUS "Prebuilt MySQL Qt driver zip already exists, skipping download")
endif()
# Extract zip
file(ARCHIVE_EXTRACT
INPUT ${QMYSQL_ZIP}
DESTINATION ${QMYSQL_EXTRACT_DIR}
)
message(STATUS "Extracted prebuilt MySQL Qt driver at ${QMYSQL_EXTRACT_DIR}. Deps will be deploeyd after build.")
# deploy
add_custom_command(TARGET noggit POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${QMYSQL_EXTRACT_DIR}"
"$<TARGET_FILE_DIR:noggit>"
)
#add distribution themes
add_custom_command(TARGET noggit POST_BUILD
@@ -452,11 +497,6 @@ IF(APPLE)
TARGET_LINK_LIBRARIES (noggit "-framework Cocoa" "-framework AppKit" "-framework Foundation")
ENDIF()
IF(MYSQL_LIBRARY AND MYSQLCPPCONN_LIBRARY AND MYSQLCPPCONN_INCLUDE)
TARGET_LINK_LIBRARIES(noggit ${MYSQL_LIBRARY} ${MYSQLCPPCONN_LIBRARY})
TARGET_INCLUDE_DIRECTORIES(noggit SYSTEM PRIVATE ${MYSQLCPPCONN_INCLUDE})
ENDIF()
IF(NOGGIT_LOGTOCONSOLE AND WIN32)
SET_PROPERTY(TARGET noggit APPEND PROPERTY LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
SET_PROPERTY(TARGET noggit APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:Debug>:"_CONSOLE">)

View File

@@ -1,166 +0,0 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <mysql/mysql.h>
#include <noggit/world.h>
#include <QSettings>
#include <QMessageBox>
#include <cppconn/driver.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/exception.h>
namespace
{
std::unique_ptr<sql::Connection> connect()
{
QSettings settings;
// if using release SQL binaries in debug mode it will crash https://bugs.mysql.com/bug.php?id=91238 unless using sql strings
// tcp://127.0.0.1:3306
const sql::SQLString hostname = "tcp://" + settings.value("project/mysql/server").toString().toStdString() + ":" + settings.value("project/mysql/port", "3306").toString().toStdString();
const sql::SQLString userName = settings.value("project/mysql/user").toString().toStdString();
const sql::SQLString password = settings.value("project/mysql/pwd").toString().toStdString();
const sql::SQLString schema = settings.value("project/mysql/db").toString().toStdString();
try
{
std::unique_ptr<sql::Connection> Con(get_driver_instance()->connect(hostname, userName, password));
// crete database if it doesn't exist
std::string createdb_statement = "CREATE DATABASE IF NOT EXISTS " + schema;
std::unique_ptr<sql::PreparedStatement> dbpstmt(Con->prepareStatement(createdb_statement));
std::unique_ptr<sql::ResultSet> res(dbpstmt->executeQuery());
Con->setSchema(schema);
// create table if it doesn't exist, querries from src/sql
std::unique_ptr<sql::PreparedStatement> tablepstmt(Con->prepareStatement("CREATE TABLE IF NOT EXISTS `UIDs` ("
"`_map_id` int(11) NOT NULL,"
"`UID` int(11) NOT NULL,"
"PRIMARY KEY(`_map_id`)"
") ENGINE = InnoDB DEFAULT CHARSET = latin1;"));
std::unique_ptr<sql::ResultSet> tableres(tablepstmt->executeQuery());
return Con;
}
catch (sql::SQLException& e)
{
return nullptr;
}
catch (std::exception& e)
{
std::cerr << "SQL Other exception: " << e.what() << std::endl;
return nullptr;
}
}
}
namespace mysql
{
bool testConnection(bool report_only_err)
{
QSettings settings;
// if using release SQL binaries in debug mode it will crash https://bugs.mysql.com/bug.php?id=91238 unless using sql strings
const sql::SQLString hostname = "tcp://" + settings.value("project/mysql/server").toString().toStdString() + ":" + settings.value("project/mysql/port", "3306").toString().toStdString();
const sql::SQLString userName = settings.value("project/mysql/user").toString().toStdString();
const sql::SQLString password = settings.value("project/mysql/pwd").toString().toStdString();
QMessageBox prompt;
prompt.setWindowFlag(Qt::WindowStaysOnTopHint);
try
{
sql::Driver* driver = get_driver_instance();
sql::Connection* connection = driver->connect(hostname, userName, password);
std::unique_ptr<sql::Connection> Con(connection);
prompt.setIcon(QMessageBox::Information);
prompt.setText("Succesfully connected to MySQL database.");
prompt.setWindowTitle("Success");
if (!report_only_err)
prompt.exec();
return true;
}
catch (sql::SQLException& e)
{
prompt.setIcon(QMessageBox::Warning);
prompt.setText("Failed to load MySQL database, check your settings. \nIf you did not intend to use this feature, disable it in Noggit->settings->MySQL");
prompt.setWindowTitle("Noggit Database Error");
// disable if connection is not valid
// settings.value("project/mysql/enabled") = false;
std::stringstream promptText;
promptText << "\n# ERR: " << e.what();
promptText << "\n (MySQL error code: " << e.getErrorCode() + ")";
prompt.setInformativeText(promptText.str().c_str());
prompt.exec();
return false;
}
catch (std::exception& e)
{
std::cerr << "SQL Other exception: " << e.what() << std::endl;
return false;
}
}
bool hasMaxUIDStoredDB(std::size_t mapID)
{
auto Con(connect());
if (Con == nullptr)
return false;
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("SELECT * FROM `UIDs` WHERE `_map_id`=(?)"));
pstmt->setInt(1, mapID);
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
return res->rowsCount();
}
std::uint32_t getGUIDFromDB(std::size_t mapID)
{
auto Con(connect());
if (Con == nullptr)
return 0;
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("SELECT `UID` FROM `UIDs` WHERE `_map_id`=(?)"));
pstmt->setInt(1, mapID);
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
std::uint32_t highGUID(0);
if (res->rowsCount() == 0)
{
return 0;
}
while (res->next())
{
highGUID = res->getInt(1);
}
return highGUID;
}
void insertUIDinDB(std::size_t mapID, std::uint32_t NewUID)
{
auto Con(connect());
if (Con == nullptr)
return;
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("INSERT INTO `UIDs` SET `_map_id`=(?), `UID`=(?)"));
pstmt->setInt(1, mapID);
pstmt->setInt(2, NewUID);
pstmt->executeUpdate();
}
void updateUIDinDB (std::size_t mapID, std::uint32_t NewUID)
{
auto Con(connect());
if (Con == nullptr)
return;
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("UPDATE `UIDs` SET `UID`=(?) WHERE `_map_id`=(?)"));
pstmt->setInt(1, NewUID);
pstmt->setInt(2, mapID);
pstmt->executeUpdate();
}
}

View File

@@ -1,15 +0,0 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <cinttypes>
#include <cstddef>
namespace mysql
{
bool testConnection(bool report_only_err = false);
bool hasMaxUIDStoredDB(std::size_t mapID);
std::uint32_t getGUIDFromDB(std::size_t mapID);
void insertUIDinDB(std::size_t mapID, std::uint32_t NewUID);
void updateUIDinDB (std::size_t mapID, std::uint32_t NewUID);
};

View File

@@ -76,11 +76,8 @@
#include <noggit/tools/AreaTriggerTool.hpp>
#include <noggit/StringHash.hpp>
#include <noggit/application/NoggitApplication.hpp>
#include <noggit/sql/DatabaseManager.h>
#ifdef USE_MYSQL_UID_STORAGE
#include <mysql/mysql.h>
#endif
#include <QtCore/QSettings>
#include <noggit/scripting/scripting_tool.hpp>
@@ -2472,17 +2469,23 @@ void MapView::createGUI()
set_editing_mode (editing_mode::ground);
// do we need to do this every tick ?
#ifdef USE_MYSQL_UID_STORAGE
if (_settings->value("project/mysql/enabled").toBool())
{
if (mysql::hasMaxUIDStoredDB(_world->getMapID()))
{
_status_database->setText("MySQL UID sync enabled: "
+ _settings->value("project/mysql/server").toString() + ":"
+ _settings->value("project/mysql/port").toString());
}
auto& db_mgr = Noggit::Sql::DatabaseManager::instance();
if (db_mgr.testConnection(Noggit::Sql::SQLDbType::Noggit))
{
_status_database->setText("UID SQL Database is active: "
+ _settings->value("project/mysql/server").toString() + ":"
+ _settings->value("project/mysql/port").toString());
}
else
{
_status_database->setText("UID SQL Database is not working: "
+ _settings->value("project/mysql/server").toString() + ":"
+ _settings->value("project/mysql/port").toString());
}
}
#endif
}
void MapView::on_exit_prompt()

View File

@@ -9,13 +9,11 @@
#include <noggit/ActionManager.hpp>
#include <noggit/Action.hpp>
#include <noggit/project/CurrentProject.hpp>
#ifdef USE_MYSQL_UID_STORAGE
#include <mysql/mysql.h>
#endif
#include <noggit/map_index.hpp>
#include <noggit/uid_storage.hpp>
#include <noggit/application/NoggitApplication.hpp>
#include <ClientFile.hpp>
#include <noggit/sql/SqlUIDStorage.h>
#include <QtCore/QSettings>
#include <QByteArray>
@@ -776,14 +774,13 @@ uint32_t MapIndex::newGUID()
{
std::unique_lock<std::mutex> lock (_mutex);
#ifdef USE_MYSQL_UID_STORAGE
QSettings settings;
if (settings.value ("project/mysql/enabled", false).toBool())
{
mysql::updateUIDinDB(_map_id, highestGUID + 1); // update the highest uid in db, note that if the user don't save these uid won't be used (not really a problem tho)
// update the highest uid in db, note that if the user don't save these uid won't be used (not really a problem tho)
Noggit::Sql::SqlUIDStorage::updateUIDinDB(_map_id, highestGUID + 1);
}
#endif
return ++highestGUID;
}
@@ -1135,21 +1132,19 @@ void MapIndex::searchMaxUID()
void MapIndex::saveMaxUID()
{
#ifdef USE_MYSQL_UID_STORAGE
QSettings settings;
if (settings.value ("project/mysql/enabled", false).toBool())
{
if (mysql::hasMaxUIDStoredDB(_map_id))
if (Noggit::Sql::SqlUIDStorage::hasMaxUIDStoredDB(_map_id))
{
mysql::updateUIDinDB(_map_id, highestGUID);
Noggit::Sql::SqlUIDStorage::updateUIDinDB(_map_id, highestGUID);
}
else
{
mysql::insertUIDinDB(_map_id, highestGUID);
Noggit::Sql::SqlUIDStorage::insertUIDinDB(_map_id, highestGUID);
}
}
#endif
// save the max UID on the disk (always save to sync with the db if used
uid_storage::saveMaxUID (_map_id, highestGUID);
}
@@ -1157,16 +1152,14 @@ void MapIndex::saveMaxUID()
void MapIndex::loadMaxUID()
{
highestGUID = uid_storage::getMaxUID (_map_id);
#ifdef USE_MYSQL_UID_STORAGE
QSettings settings;
if (settings.value ("project/mysql/enabled", false).toBool())
{
highestGUID = std::max(mysql::getGUIDFromDB(_map_id), highestGUID);
highestGUID = std::max(Noggit::Sql::SqlUIDStorage::getGUIDFromDB(_map_id), highestGUID);
// save to make sure the db and disk uid are synced
saveMaxUID();
}
#endif
}
void MapIndex::loadMinimapMD5translate()

View File

@@ -259,6 +259,11 @@ namespace Noggit::Project
_projectWriter = std::make_shared<ApplicationProjectWriter>();
}
unsigned int NoggitProject::buildId()
{
return ClientDatabase->getBuild();
}
void NoggitProject::createBookmark(const NoggitProjectBookmarkMap& bookmark)
{
Bookmarks.push_back(bookmark);

View File

@@ -117,6 +117,8 @@ namespace Noggit::Project
NoggitExtraMapData ExtraMapData;
NoggitProject();
unsigned int buildId();
void createBookmark(const NoggitProjectBookmarkMap& bookmark);
void deleteBookmark();

View File

@@ -0,0 +1,540 @@
#include "ClientDatabase.h"
#include <noggit/project/CurrentProject.hpp>
#include <noggit/application/Utils.hpp>
#include <QString>
#include <QSqlRecord>
#include <QSqlField>
#include <QElapsedTimer>
namespace Noggit
{
std::optional<Structures::BlizzardDatabaseRow> ClientDatabase::getRowById(const std::string& tableName, unsigned int id)
{
bool setting_use_sql_db = false; // todo QSETTING
auto row = Structures::BlizzardDatabaseRow(-1);
if (setting_use_sql_db)
row = sqlRowById(tableName, id);
else
row = clientRowById(tableName, id);
if (row.RecordId == -1)
return std::nullopt;
else
return row;
}
bool ClientDatabase::testUploadDBCtoDB(BlizzardDatabaseLib::BlizzardDatabaseTable& table)
{
auto& db_mgr = Noggit::Sql::DatabaseManager::instance();
bool valid_conn = db_mgr.testConnection(Noggit::Sql::SQLDbType::Noggit);
if (!valid_conn)
return false;
auto table_name = table.Name();
// Noggit::Project::CurrentProject::get()->projectVersion; // expension, not exact build id
unsigned int build_id = Noggit::Project::CurrentProject::get()->buildId();
// check if table exists
QString sql_table_name = getSqlTableName(table_name, build_id).c_str();
auto noggit_db = db_mgr.noggitDatabase();
// table integrity check
bool table_is_valid = true;
bool fresh_table = false;
QSqlRecord sql_rec = noggit_db.record(sql_table_name);
// noggit_db.tables().contains(sql_table_name) is bugged with current qt version and mysql 8
QSqlQuery query_show(noggit_db);
if (!query_show.exec("SHOW TABLES"))
{
qWarning() << "Failed to list tables:" << query_show.lastError().text();
return false;
}
QStringList tables;
while (query_show.next())
{
tables << query_show.value(0).toString();
}
if (tables.contains(sql_table_name))
{
// this is also bugged...
// if (sql_rec.isEmpty())
// {
// table_is_valid = false;
// }
// else
// {
// // TODO verify db structure, just column count for now
// if (table.ColumnCount() != sql_rec.count())
// {
// assert(false);
// table_is_valid = false;
// }
// }
}
else // table doesn't exist
{
// create table
table_is_valid = createSQLTableIfNotExist(table);
fresh_table = true;
}
if (!table_is_valid)
{
qDebug() << "Table " << sql_table_name << "does not exist or has wrong structure.";
return false;
}
// insert if fresh_table, otherwise replace?
// TODOOOOOOOOOOOOOOOOOOOOOOO : fill db with data
auto row_definition = table.GetRecordDefinition();
auto sql_record_format = recordFormat(table_name);
auto client_table_iterator = table.Records();
QStringList column_names;
for (auto& sql_column_format : sql_record_format)
{
column_names.append(sql_column_format.Name.c_str());
}
// INSERT INTO table (col1, col2, ...) VALUES (?, ?, ...)
QString sql = QString("INSERT INTO `%1` (%2) VALUES (%3)")
.arg(sql_table_name)
.arg(column_names.join(", "))
.arg(QString("?, ").repeated(column_names.size()).chopped(2));
QSqlQuery query(noggit_db);
if (!query.prepare(sql))
{
qWarning() << "Prepare failed:" << query.lastError().text();
return false;
}
QElapsedTimer timer;
timer.start();
// using transaction to speed up bulk query
// noggit_db.transaction();
// batching:
// One QVariantList per column
std::vector<QVariantList> columnData(column_names.size());
while (client_table_iterator.HasRecords())
{
auto& record = client_table_iterator.Next();
int colIndex = 0;
for (auto& column_def : row_definition.ColumnDefinitions)
{
if (column_def.Type == "int" && column_def.isID) // id column isn't saved in the map
{
// query.addBindValue(record.RecordId);
columnData[colIndex++].append(record.RecordId); // batching
continue;
}
auto& rowColumn = record.Columns.at(column_def.Name);
if (column_def.Type == "int")
{
if (column_def.arrLength > 1)
{
for (int i = 0; i < column_def.arrLength; i++)
{
// int Value = std::stoi(rowColumn.Values[i]);
// query.addBindValue(QString::fromStdString(rowColumn.Values[i]).toInt());
columnData[colIndex++].append(QString::fromStdString(rowColumn.Value).toInt());
}
}
else
{
// int Value = std::stoi(rowColumn.Value);
// query.addBindValue(QString::fromStdString(rowColumn.Value).toInt());
columnData[colIndex++].append(QString::fromStdString(rowColumn.Value).toInt());
}
}
else if (column_def.Type == "float")
{
if (column_def.arrLength > 1)
{
for (int i = 0; i < column_def.arrLength; i++)
{
// float Value = std::stof(rowColumn.Values[i]);
// query.addBindValue(QString::fromStdString(rowColumn.Values[i]).toFloat());
columnData[colIndex++].append(QString::fromStdString(rowColumn.Values[i]).toFloat());
}
}
else
{
// float Value = std::stof(rowColumn.Value);
// query.addBindValue(QString::fromStdString(rowColumn.Value).toFloat());
columnData[colIndex++].append(QString::fromStdString(rowColumn.Value).toFloat());
}
}
else if (column_def.Type == "string")
{
if (column_def.arrLength > 1)
{
for (int i = 0; i < column_def.arrLength; i++)
{
// std::string value = rowColumn.Values[i];
// query.addBindValue(QString::fromStdString(rowColumn.Values[i]));
columnData[colIndex++].append(QString::fromStdString(rowColumn.Values[i]));
}
}
else
{
// std::string Value = rowColumn.Value;
// query.addBindValue(QString::fromStdString(rowColumn.Value));
columnData[colIndex++].append(QString::fromStdString(rowColumn.Value));
}
}
else if (column_def.Type == "locstring")
{
for (int i = 0; i < 16; i++)
{
// query.addBindValue(QString::fromStdString(rowColumn.Values[i]));
columnData[colIndex++].append(QString::fromStdString(rowColumn.Values[i]));
}
auto& flagValue = record.Columns.at(column_def.Name + "_flags");
// query.addBindValue(QString::fromStdString(flagValue.Value).toInt());
columnData[colIndex++].append(QString::fromStdString(flagValue.Value).toInt());
}
}
// if (!query.exec())
// {
// qWarning() << "Insert failed:" << query.lastError().text();
// noggit_db.rollback();
// return false;
// }
}
// Bind all columnData at once
for (auto& col : columnData)
query.addBindValue(col);
noggit_db.transaction();
if (!query.execBatch(QSqlQuery::ValuesAsColumns))
{
qWarning() << "Batch insert failed:" << query.lastError().text();
noggit_db.rollback();
return false;
}
noggit_db.commit();
qint64 elapsedMs = timer.elapsed();
qDebug() << "Inserted" << table.RecordCount() << "rows in"
<< elapsedMs << "ms ("
<< (table.RecordCount() * 1000.0 / elapsedMs) << " rows/sec)";
return true;
}
// get from local dbc data memory stream in BlizzardDatabaseLib::BlizzardDatabase
Structures::BlizzardDatabaseRow ClientDatabase::clientRowById(const std::string& tableName, unsigned int id)
{
auto& table = Noggit::Project::CurrentProject::get()->ClientDatabase->LoadTable(tableName, readFileAsIMemStream);
auto record = table.RecordById(id);
return record;
}
// get from SQL request to noggit db
// never use this function for more than 1 rows, implement a new bulk function
Structures::BlizzardDatabaseRow ClientDatabase::sqlRowById(const std::string& tableName, unsigned int id)
{
auto& db_mgr = Noggit::Sql::DatabaseManager::instance();
// Test connection ?
auto noggit_db = db_mgr.noggitDatabase();
auto row_definition = Noggit::Project::CurrentProject::get()->ClientDatabase->TableRecordDefinition(tableName);
QString sql_table_name = getSqlTableName(tableName).c_str();
QSqlQuery query(noggit_db);
QString sql = QString("SELECT * FROM %1 WHERE ID = :id").arg(sql_table_name);
query.prepare(sql);
query.bindValue(":id", id);
if (!query.exec())
{
qWarning() << "Query exec failed:" << query.lastError().text();
return BlizzardDatabaseLib::Structures::BlizzardDatabaseRow();
}
if (query.next())
{
QSqlRecord record = query.record();
auto database_row = Structures::BlizzardDatabaseRow(id);
// test db def/////////////////////////
{
auto record_db_def = recordFormat(tableName);
if (record.count() != record_db_def.size())
{
// error : definition deosn't match db structure
assert(false);
return Structures::BlizzardDatabaseRow(-1);
}
// Tests construct row from query
// TODOOOOOOOOOOOOOOO
for (int i = 0; i < record_db_def.size(); ++i)
{
auto& column_db_def = record_db_def[i];
QSqlField db_field = record.field(i);
assert(column_db_def.Name == record.fieldName(i).toStdString());
// assert(column_db_def.Type == db_field.type());
}
}/////////////////////////////////////////
int field_idx = 0;
for (int column_def_idx = 0; column_def_idx < row_definition.ColumnDefinitions.size(); ++column_def_idx)
{
auto& column_def = row_definition.ColumnDefinitions[column_def_idx];
auto database_column = Structures::BlizzardDatabaseColumn();
auto value = std::string();
if (column_def.Type == "locstring")
{
std::vector<std::string> localizedValues = std::vector<std::string>();
for (int loc_idx = 0; loc_idx < 16; loc_idx++)
{
localizedValues.push_back(query.value(field_idx++).toString().toStdString());
}
database_column.Values = localizedValues;
database_row.Columns[column_def.Name] = database_column;
// currently loc mask is set to a separate column because wdbc reader does it.
auto loc_mask_column = Structures::BlizzardDatabaseColumn();
loc_mask_column.Value = query.value(field_idx++).toString().toStdString();
database_row.Columns[column_def.Name + "_flags"] = loc_mask_column;
}
else // every other type than locstring
{
if (column_def.arrLength > 1) // array
{
for (int i = 0; i < column_def.arrLength; i++)
{
auto Value = query.value(field_idx++);
database_column.Values.push_back(Value.toString().toStdString());
}
}
else // single value
{
auto Value = query.value(field_idx++);
value = Value.toString().toStdString();
}
}
database_column.Value = value;
database_row.Columns[column_def.Name] = database_column;
}
return database_row;
}
else
{
qWarning() << "No row found in" << tableName.c_str() << "for ID =" << id;
return BlizzardDatabaseLib::Structures::BlizzardDatabaseRow();
}
}
bool ClientDatabase::createSQLTableIfNotExist(const BlizzardDatabaseLib::BlizzardDatabaseTable& table)
{
const std::string table_name = table.Name();
auto row_definition = table.GetRecordDefinition();
const std::string sql_table_name = getSqlTableName(table_name);
std::string statement = std::format("CREATE TABLE IF NOT EXISTS `{}` (", sql_table_name);
std::string primary_key_name;
auto db_record_format = recordFormat(table_name);
assert(db_record_format.size() == table.ColumnCount());
for (auto& db_column_format : db_record_format)
{
statement += std::format("`{}` {}", db_column_format.Name, db_column_format.Type);
if (db_column_format.Type == "TEXT")
{
statement += " NULL"; // allow NULL by default
}
else
{
if (!db_column_format.isSigned && db_column_format.Type == "INT")
{
// assert(db_column_format.Type == "INT");
statement += " UNSIGNED"; // only allow int to be unsigned?
}
statement += " NOT NULL"; // allow text to be nulled
statement += " DEFAULT 0";
}
statement += ",\n";
if (db_column_format.isID)
{
assert(primary_key_name.empty()); // more than one key ? TODO
primary_key_name = db_column_format.Name;
}
}
if (!primary_key_name.empty())
statement += std::format("PRIMARY KEY (`{}`)", primary_key_name);
// Add indexes for relations
for (auto& db_column_format : db_record_format)
{
if (db_column_format.isRelation && !db_column_format.isID) {
statement += std::format(",\nINDEX (`{}`)", db_column_format.Name);
}
}
// statement += ")\n ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 DEFAULT COLLATE='utf8mb4_general_ci';";
statement += ")\n ENGINE = InnoDB;";
auto& db_mgr = Noggit::Sql::DatabaseManager::instance();
bool valid_conn = db_mgr.testConnection(Noggit::Sql::SQLDbType::Noggit);
if (!valid_conn)
return false;
auto noggit_db = db_mgr.noggitDatabase();
QSqlQuery query(noggit_db);
bool success = query.exec(QString::fromStdString(statement));
if (!success)
{
qDebug() << "Failed to create table:" << query.lastError().text();
}
else
{
qDebug() << "Table " << table_name.c_str() << " created or already exists.";
}
return success;
}
std::string ClientDatabase::getSqlTableName(const std::string& db_name, unsigned int build_id)
{
if (build_id == 0)
build_id = Noggit::Project::CurrentProject::get()->buildId();
std::string table = std::format("db_{}_{}", db_name, build_id);
// convert to lowercase for compatibility with SQL
std::transform(table.begin(), table.end(), table.begin(),
[](unsigned char c) { return std::tolower(c); });
return table;
}
std::vector<DbColumnFormat> ClientDatabase::recordFormat(const std::string& table_name)
{
auto record_format = std::vector<DbColumnFormat>();
auto row_definition = Noggit::Project::CurrentProject::get()->ClientDatabase->TableRecordDefinition(table_name);
for (int col_idx = 0; col_idx < row_definition.ColumnDefinitions.size(); col_idx++)
{
auto& column_def = row_definition.ColumnDefinitions[col_idx];
bool is_locstring = false;
// convert dbd definition type names to real format
// TODO : map types
std::string sql_data_type = "INT";
if (BlizzardDatabaseLib::Extension::String::Compare(column_def.Type, "int"))
{
sql_data_type = "INT";
}
else if (BlizzardDatabaseLib::Extension::String::Compare(column_def.Type, "float"))
{
sql_data_type = "FLOAT";
}
else if (BlizzardDatabaseLib::Extension::String::Compare(column_def.Type, "string"))
{
sql_data_type = "TEXT";
}
else if (BlizzardDatabaseLib::Extension::String::Compare(column_def.Type, "locstring"))
{
sql_data_type = "TEXT";
is_locstring = true;
}
else
assert(false);
int array_size = 1;
if (column_def.arrLength > 1)
{
array_size = column_def.arrLength;
}
if (is_locstring)
array_size = 16;
for (int i = 0; i < array_size; i++)
{
DbColumnFormat db_col_format;
std::string col_name = "";
if (array_size == 1)
{
col_name = column_def.Name;
}
else if (is_locstring)
{
col_name = std::format("{}_{}", column_def.Name, dbc_string_loc_names[i]); // {MapName_lang}_{enUS}
}
else if (array_size > 1)
{
col_name = std::format("{}_{}", column_def.Name, i); // {MapName}_{0}
}
db_col_format.Name = col_name;
db_col_format.Type = sql_data_type;
assert(!(column_def.isID && array_size > 1));
db_col_format.isID = column_def.isID;
db_col_format.isRelation = column_def.isRelation;
db_col_format.isSigned = column_def.isSigned;
record_format.push_back(db_col_format);
}
if (is_locstring) // add lang mask column
{
DbColumnFormat db_col_format;
db_col_format.Name = std::format("{}_flags", column_def.Name);
db_col_format.Type = "INT";
db_col_format.isSigned = false;
db_col_format.isID = false;
db_col_format.isRelation = false;
record_format.push_back(db_col_format);
}
}
return record_format;
}
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include <blizzard-database-library/include/BlizzardDatabase.h>
#include <blizzard-database-library/include/structures/FileStructures.h>
#include <noggit/sql/DatabaseManager.h>
#include <optional>
constexpr const char* dbc_string_loc_names[16] = { "enUS", "koKR", "frFR", "deDE", "zhCN",
"zhTW", "esES", "esMX", "ruRU", "jaJP", "ptPT", "itIT",
"unk_12", "unk_13", "unk_14", "unk_15" };
using namespace BlizzardDatabaseLib;
namespace Noggit
{
struct DbColumnFormat
{
std::string Type = "";
std::string Name = "";
// int size;
bool isID = false;
bool isRelation = false;
bool isSigned = true;
};
// calls client or server db adaptively. so /sql/ is not really a good location
class ClientDatabase
{
public:
// static ClientDatabase& instance()
// {
// static ClientDatabase _instance;
// return _instance;
// }
static std::optional<Structures::BlizzardDatabaseRow> getRowById(const std::string& tableName, unsigned int id); // constructs a row either from db or client
static bool testUploadDBCtoDB(BlizzardDatabaseLib::BlizzardDatabaseTable& table);
static void TODODeploySqlToClient();
private:
// ClientDatabase() = default;
// ~ClientDatabase() = default;
// ClientDatabase(const ClientDatabase&) = delete;
// ClientDatabase& operator=(const ClientDatabase&) = delete;
static Structures::BlizzardDatabaseRow clientRowById(const std::string& tableName, unsigned int id);
static Structures::BlizzardDatabaseRow sqlRowById(const std::string& tableName, unsigned int id);
static bool createSQLTableIfNotExist(const BlizzardDatabaseLib::BlizzardDatabaseTable& table);
static std::string getSqlTableName(const std::string& db_name, unsigned int build_id = 0); // get automatically from project if default(0)
static std::vector<DbColumnFormat> recordFormat(const std::string& table_name); // true record format for all columns, not array size/loc etc. eg returns all 17 columns for loc.
};
}

View File

View File

@@ -0,0 +1,236 @@
#pragma once
#include <QSqlDatabase>
#include <QString>
#include <QSqlError>
#include <QDebug>
#include <QSqlQuery>
#include <QMap>
#include <QStringList>
#include <QMessageBox>
// Connections are currently not thread safe ! would need temporary connection copies per thread.
namespace Noggit::Sql
{
enum class SQLDbType
{
Noggit,
World
};
enum class SqlDriver
{
MySQL,
// SQLite,
// PostgreSQL
};
struct DbConfig
{
QString host;
int port = 3306;
QString dbName;
QString user;
QString password;
};
class DatabaseManager
{
public:
static DatabaseManager& instance()
{
static DatabaseManager _instance;
return _instance;
}
bool initializeDb(SQLDbType type,
const QString& host, int port,
const QString& dbName, const QString& user, const QString& password)
{
DbConfig newConfig{ host, port, dbName, user, password };
// If config is unchanged and connection is alive, nothing to do
if (configs[type].host == host &&
configs[type].port == port &&
configs[type].dbName == dbName &&
configs[type].user == user &&
configs[type].password == password &&
testConnection(type))
{
return true;
}
configs[type] = newConfig;
QString connName = connectionName(type);
// remove old connection if it exists
if (QSqlDatabase::contains(connName))
{
QSqlDatabase oldDb = QSqlDatabase::database(connName);
if (oldDb.isOpen())
oldDb.close();
QSqlDatabase::removeDatabase(connName);
}
// Create new connection
QSqlDatabase db = QSqlDatabase::addDatabase(driverToString(SqlDriver::MySQL), connName);
db.setHostName(host);
db.setPort(port);
// db.setDatabaseName(dbName);
db.setUserName(user);
db.setPassword(password);
// connect to my sql without a database
if (!db.open())
{
qWarning() << "Failed to connect to database " << dbName << "on host" << host << ":" << db.lastError().text();
return false;
}
// Create database if it doesn't exist
QSqlQuery query(db);
if (!query.exec(QStringLiteral("CREATE DATABASE IF NOT EXISTS `%1`").arg(dbName)))
{
qWarning() << "Failed to open database " << dbName << ":" << db.lastError().text();
return false;
}
// now attempt to connect to db
// Need to close connection first because it needs to create the connection with a databasename.
db.close();
db.setDatabaseName(dbName);
if (!db.open())
{
qDebug() << "Failed to open database:" << db.lastError().text();
return false;
}
qDebug() << db.tables();
return true;
}
// requires database to be initialized
bool testConnection(SQLDbType type) const
{
if (!configs.contains(type))
{
qWarning() << "No configuration for database type" << static_cast<int>(type);
}
QSqlDatabase db = database(type);
if (db.isValid() && db.isOpen())
return true;
// If the connection is invalid or closed, attempt to open it
if (!db.open())
{
qWarning() << "Connection test failed for" << db.connectionName() << ":" << db.lastError().text();
qDebug() << db.lastError().nativeErrorCode();
qDebug() << db.lastError().databaseText();
return false;
}
else
{
qDebug() << "Reconnected to database" << db.connectionName();
}
return true;
}
bool testConnectionWithPopup(SQLDbType type, bool report_only_error) const
{
QMessageBox prompt;
prompt.setWindowFlag(Qt::WindowStaysOnTopHint);
bool success = false;
std::stringstream promptText;
try
{
success = testConnection(type);
}
catch (std::exception& e)
{
success = false;
promptText << "\n# ERR: " << e.what();
}
if (success && !report_only_error)
{
prompt.setIcon(QMessageBox::Information);
prompt.setText("Succesfully connected to MySQL database.");
prompt.setWindowTitle("Success");
if (!report_only_error)
prompt.exec();
}
else if (!success)
{
prompt.setIcon(QMessageBox::Warning);
prompt.setText("Failed to load MySQL database, check your settings. \nIf you did not intend to use this feature, disable it in Noggit->settings->MySQL");
prompt.setWindowTitle("Noggit Database Error");
// disable if connection is not valid
// settings.value("project/mysql/enabled") = false;
promptText << database(type).lastError().text().toStdString() << std::endl;
promptText << database(type).lastError().nativeErrorCode().toStdString() << std::endl;
promptText << database(type).lastError().databaseText().toStdString() << std::endl;
prompt.setInformativeText(promptText.str().c_str());
prompt.exec();
}
return success;
}
bool isDriverAvailable()
{
QStringList availableDrivers = QSqlDatabase::drivers();
return availableDrivers.contains(driverToString(_sql_driver), Qt::CaseInsensitive);
}
QSqlDatabase database(SQLDbType type) const
{
return QSqlDatabase::database(connectionName(type));
}
QSqlDatabase noggitDatabase() const
{
return DatabaseManager::database(SQLDbType::Noggit);
}
QSqlDatabase worldDatabase() const
{
return DatabaseManager::database(SQLDbType::World);
}
private:
DatabaseManager() = default;
~DatabaseManager() = default;
DatabaseManager(const DatabaseManager&) = delete;
DatabaseManager& operator=(const DatabaseManager&) = delete;
SqlDriver _sql_driver = SqlDriver::MySQL;
mutable QMap<SQLDbType, DbConfig> configs; // stores current configuration per DB type
QString connectionName(SQLDbType type) const
{
switch (type)
{
case SQLDbType::Noggit: return "noggit";
case SQLDbType::World: return "world";
}
return {};
}
QString driverToString(SqlDriver driver)
{
switch (driver)
{
case SqlDriver::MySQL: return "QMYSQL";
// case SqlDriver::SQLite: return "QSQLITE";
// case SqlDriver::PostgreSQL: return "QPSQL";
}
return "";
}
};
}

View File

@@ -0,0 +1,107 @@
#include "SqlUIDStorage.h"
bool Noggit::Sql::SqlUIDStorage::hasMaxUIDStoredDB(std::size_t mapID)
{
auto& db_mgr = DatabaseManager::instance();
bool valid_conn = db_mgr.testConnection(SQLDbType::Noggit);
if (!valid_conn)
return false;
auto noggit_db = db_mgr.noggitDatabase();
QSqlQuery query(noggit_db);
query.prepare(QStringLiteral("SELECT * FROM `UIDs` WHERE `_map_id` = ?"));
query.addBindValue(static_cast<int>(mapID));
if (!query.exec())
{
qWarning() << "Failed to check UIDs:" << query.lastError().text();
return false;
}
return query.next(); // true if at least one row exists
}
std::uint32_t Noggit::Sql::SqlUIDStorage::getGUIDFromDB(std::size_t mapID)
{
auto& db_mgr = Sql::DatabaseManager::instance();
bool valid_conn = db_mgr.testConnection(SQLDbType::Noggit);
if (!valid_conn)
return 0;
QSqlDatabase noggit_db = db_mgr.noggitDatabase();
/////
QSqlQuery query(noggit_db);
query.prepare(QStringLiteral("SELECT `UID` FROM `UIDs` WHERE `_map_id` = ?"));
query.addBindValue(static_cast<int>(mapID));
if (!query.exec())
{
qWarning() << "Failed to fetch UID from db:" << query.lastError().text();
return 0;
}
if (query.next())
return query.value(0).toUInt();
return 0; // no rows
// Optional, find highest GUID of all maps.
// "SELECT `UID` FROM `UIDs`"
// std::uint32_t highGUID(0);
// if (res->rowsCount() == 0)
// {
// return 0;
// }
// while (res->next())
// {
// highGUID = res->getInt(1);
// }
//
//
// return highGUID;
}
void Noggit::Sql::SqlUIDStorage::insertUIDinDB(std::size_t mapID, std::uint32_t NewUID)
{
auto& db_mgr = Sql::DatabaseManager::instance();
bool valid_conn = db_mgr.testConnection(SQLDbType::Noggit);
if (!valid_conn)
{
return;
}
QSqlDatabase noggit_db = db_mgr.noggitDatabase();
QSqlQuery query(noggit_db);
query.prepare(QStringLiteral("INSERT INTO `UIDs` (`_map_id`, `UID`) VALUES (?, ?)"));
query.addBindValue(static_cast<int>(mapID));
query.addBindValue(static_cast<int>(NewUID));
if (!query.exec())
{
qWarning() << "Failed to insert UID in db: " << query.lastError().text();
}
}
void Noggit::Sql::SqlUIDStorage::updateUIDinDB(std::size_t mapID, std::uint32_t NewUID)
{
auto& db_mgr = Sql::DatabaseManager::instance();
if (!db_mgr.testConnection(SQLDbType::Noggit))
return;
QSqlDatabase noggit_db = db_mgr.noggitDatabase();
QSqlQuery query(noggit_db);
query.prepare(QStringLiteral("UPDATE `UIDs` SET `UID` = ? WHERE `_map_id` = ?"));
query.addBindValue(static_cast<int>(NewUID));
query.addBindValue(static_cast<int>(mapID));
if (!query.exec())
{
qWarning() << "Failed to update UID:" << query.lastError().text();
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <noggit/sql/DatabaseManager.h>
namespace Noggit::Sql
{
class SqlUIDStorage
{
public:
static bool hasMaxUIDStoredDB(std::size_t mapID);
static std::uint32_t getGUIDFromDB(std::size_t mapID);
static void insertUIDinDB(std::size_t mapID, std::uint32_t NewUID);
static void updateUIDinDB(std::size_t mapID, std::uint32_t NewUID);
};
}

View File

@@ -14,6 +14,7 @@
#include <noggit/World.h>
#include <blizzard-database-library/include/BlizzardDatabase.h>
#include <noggit/sql/ClientDatabase.h>
#include <QApplication>
#include <QButtonGroup>
@@ -33,6 +34,7 @@
#include <QSpinBox>
#include <QStackedWidget>
#include <QWheelEvent>
#include <QtCore/QSettings>
#include <filesystem>
@@ -629,7 +631,22 @@ void MapCreationWizard::selectMap(int map_id)
// int map_id = world->getMapID();
auto table = _project->ClientDatabase->LoadTable("Map", readFileAsIMemStream);
auto record = table.Record(map_id);
auto record = table.RecordById(map_id);
/// test area, delete later
// auto table = _project->ClientDatabase->GetTable("Map");
QSettings settings;
bool use_mysql = settings.value("project/mysql/enabled", false).toBool();
if (use_mysql)
{
// bool valid_conn = mysql::testConnection(true);
Noggit::ClientDatabase::testUploadDBCtoDB(table);
}
///////////////////////////////
_cur_map_id = map_id;
@@ -762,7 +779,7 @@ void MapCreationWizard::selectMapDifficulty()
return;
auto difficulty_table = _project->ClientDatabase->LoadTable("MapDifficulty", readFileAsIMemStream);
auto record = difficulty_table.Record(selected_difficulty_id);
auto record = difficulty_table.RecordById(selected_difficulty_id);
//_difficulty_type;
_difficulty_req_message->fill(record, "Message_lang");

View File

@@ -21,6 +21,8 @@
#include <noggit/ui/windows/settingsPanel/SettingsPanel.h>
#include <noggit/uid_storage.hpp>
#include <noggit/World.h>
#include <noggit/sql/SqlUIDStorage.h>
#include <noggit/sql/ClientDatabase.h>
#include <string>
#include <blizzard-archive-library/include/Exception.hpp>
@@ -43,16 +45,11 @@
#include <QtWidgets/QTabWidget>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
#include <QtCore/QSettings>
#include <chrono>
#include <sstream>
#ifdef USE_MYSQL_UID_STORAGE
#include <mysql/mysql.h>
#include <QtCore/QSettings>
#endif
#include "revision.h"
#include "ui_TitleBar.h"
@@ -84,18 +81,32 @@ namespace Noggit::Ui::Windows
LogError << "NoggitWindow() : Unsupported project version, skipping loading DBCs." << std::endl;
}
_settings = new settings(this);
QSettings settings;
// connect to databases
bool use_mysql = settings.value("project/mysql/enabled", false).toBool();
if (use_mysql)
{
auto host = settings.value("project/mysql/server").toString();
auto port = settings.value("project/mysql/port", "3306").toInt();
auto db_name = settings.value("project/mysql/db").toString(); // schema name
auto user = settings.value("project/mysql/user").toString();
auto pass = settings.value("project/mysql/pwd").toString();
auto& db_manager = Sql::DatabaseManager::instance();
db_manager.initializeDb(Sql::SQLDbType::Noggit, host, port, db_name, user, pass);
}
setCentralWidget(_null_widget);
// The default value is AnimatedDocks | AllowTabbedDocks.
setDockOptions(AnimatedDocks | AllowNestedDocks | AllowTabbedDocks | GroupedDragging);
_about = new about(this);
_settings = new settings(this);
_menuBar = menuBar();
QSettings settings;
if (!settings.value("systemWindowFrame", true).toBool())
{
QWidget* widget = new QWidget(this);
@@ -159,16 +170,9 @@ namespace Noggit::Ui::Windows
unsigned int world_map_id = getWorld()->getMapID();
#ifdef USE_MYSQL_UID_STORAGE
bool use_mysql = settings.value("project/mysql/enabled", false).toBool();
bool valid_conn = false;
if (use_mysql)
{
valid_conn = mysql::testConnection(true);
}
if ((valid_conn && mysql::hasMaxUIDStoredDB(world_map_id))
if ((Noggit::Sql::SqlUIDStorage::hasMaxUIDStoredDB(world_map_id))
|| uid_storage::hasMaxUIDStored(world_map_id)
)
{
@@ -176,7 +180,8 @@ namespace Noggit::Ui::Windows
getWorld()->mapIndex.loadMaxUID();
enterMapAt(pos, camera_pitch, camera_yaw, uid_fix_mode::none, from_bookmark);
}
#else
// old if no mysql block
/*
if (uid_storage::hasMaxUIDStored(world_map_id))
{
if (settings.value("uid_startup_check", true).toBool())
@@ -187,8 +192,7 @@ namespace Noggit::Ui::Windows
getWorld()->mapIndex.loadMaxUID();
enterMapAt(pos, camera_pitch, camera_yaw, uid_fix_mode::none, from_bookmark);
}
}
#endif
}*/
else
{
auto uid_fix_window(new UidFixWindow(pos, camera_pitch, camera_yaw));

View File

@@ -3,6 +3,7 @@
#include <noggit/Log.h>
#include <noggit/ui/FramelessWindow.hpp>
#include <noggit/ui/windows/settingsPanel/SettingsPanel.h>
#include <noggit/sql/DatabaseManager.h>
#include <QDir>
#include <QtCore/QSettings>
@@ -10,13 +11,10 @@
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QPushButton>
#ifdef USE_MYSQL_UID_STORAGE
#include <mysql/mysql.h>
#endif
#include <ui_SettingsPanel.h>
#include <sstream>
#include <QMessageBox>
namespace Noggit
@@ -79,11 +77,9 @@ namespace Noggit
);
#ifdef USE_MYSQL_UID_STORAGE
ui->MySQL_box->setEnabled(true);
ui->MySQL_box->setCheckable(true);
ui->mysql_warning->setVisible(false);
#endif
ui->_theme->addItem("System");
@@ -170,10 +166,55 @@ namespace Noggit
connect(ui->mysql_connect_test, &QPushButton::clicked, [this]
{
save_changes();
#ifdef USE_MYSQL_UID_STORAGE
mysql::testConnection();
#endif
// test button for noggit db only !
// save_changes(); // old method saved and only used qsetting
auto& db_manager = Sql::DatabaseManager::instance();
if (!db_manager.isDriverAvailable())
{
qDebug() << "Available drivers:" << QSqlDatabase::drivers();
// SPECIAL MISSING/currupted driver DLL POPUP
QMessageBox prompt;
prompt.setWindowFlag(Qt::WindowStaysOnTopHint);
prompt.setIcon(QMessageBox::Critical);
prompt.setText("mySQL driver could not be loaded.");
prompt.setWindowTitle("Noggit Database Error");
std::stringstream promptText;
promptText << "Make sure <Noggit/sqldrivers/qsqlmysql.dll> exists.\n";
promptText << "If not, it can be downloaded at https://github.com/thecodemonkey86/qt_mysql_driver/releases \n";
promptText << "Noggit Qt version :" << QT_VERSION_STR;
prompt.setInformativeText("Make sure Noggit/sqldrivers/qsqlmysql.dll exists."
"If not, it can be downloaded at ");
prompt.exec();
return;
}
QString host = ui->_mysql_server_field->text();
QString user = ui->_mysql_user_field->text();
QString pass = ui->_mysql_pwd_field->text();
QString db_name = ui->_mysql_db_field->text();
int port = ui->_mysql_port_field->value();
// test temporary connection from text fields
bool test1 = db_manager.initializeDb(Sql::SQLDbType::Noggit, host, port, db_name, user, pass);
bool test2 = db_manager.testConnectionWithPopup(Sql::SQLDbType::Noggit, false);
// reconnect to saved db from settings
QSettings settings;
host = settings.value("project/mysql/server").toString();
port = settings.value("project/mysql/port", "3306").toInt();
db_name = settings.value("project/mysql/db").toString(); // schema name
user = settings.value("project/mysql/user").toString();
pass = settings.value("project/mysql/pwd").toString();
bool test3 = db_manager.initializeDb(Sql::SQLDbType::Noggit, host, port, db_name, user, pass);
assert(db_manager.testConnection(Sql::SQLDbType::Noggit));
}
);
@@ -234,15 +275,13 @@ namespace Noggit
ui->_wmo_aabb->setChecked(_settings->value("render/wmo_aabb", false).toBool());
ui->_wmo_group_bounds->setChecked(_settings->value("render/wmo_groups_bounds", false).toBool());
#ifdef USE_MYSQL_UID_STORAGE
ui->MySQL_box->setChecked (_settings->value ("project/mysql/enabled").toBool());
auto server_str = _settings->value("project/mysql/server", "127.0.0.1").toString();
auto user_str = _settings->value("project/mysql/user", "127.0.0.1").toString();
auto pwd_str = _settings->value("project/mysql/pwd", "127.0.0.1").toString();
auto db_str = _settings->value("project/mysql/db", "127.0.0.1").toString();
auto port_int = _settings->value("project/mysql/port", "127.0.0.1").toInt();
auto user_str = _settings->value("project/mysql/user", "root").toString();
auto pwd_str = _settings->value("project/mysql/pwd", "root").toString();
auto db_str = _settings->value("project/mysql/db", "noggit").toString();
auto port_int = _settings->value("project/mysql/port", "3306").toInt();
// set some default
if (server_str.isEmpty())
@@ -261,7 +300,6 @@ namespace Noggit
ui->_mysql_pwd_field->setText (pwd_str);
ui->_mysql_db_field->setText (db_str);
ui->_mysql_port_field->setValue (port_int);
#endif
bool wireframe_type = _settings->value("wireframe/type", false).toBool();
@@ -308,14 +346,12 @@ namespace Noggit
_settings->setValue("modern_features", ui->_modern_features->isChecked());
_settings->setValue("use_mclq_liquids_export", ui->_use_mclq_liquids_export->isChecked());
#ifdef USE_MYSQL_UID_STORAGE
_settings->setValue ("project/mysql/enabled", ui->MySQL_box->isChecked());
_settings->setValue ("project/mysql/server", ui->_mysql_server_field->text());
_settings->setValue ("project/mysql/user", ui->_mysql_user_field->text());
_settings->setValue ("project/mysql/pwd", ui->_mysql_pwd_field->text());
_settings->setValue ("project/mysql/db", ui->_mysql_db_field->text());
_settings->setValue ("project/mysql/port", ui->_mysql_port_field->text());
#endif
_settings->setValue("wireframe/type", ui->radio_wire_cursor->isChecked());
_settings->setValue("wireframe/radius", ui->_wireframe_radius->value());
@@ -338,6 +374,18 @@ namespace Noggit
_settings->sync();
// reinitialize db on save
auto& db_manager = Sql::DatabaseManager::instance();
QString host = _settings->value("project/mysql/server").toString();
int port = _settings->value("project/mysql/port", "3306").toInt();
QString db_name = _settings->value("project/mysql/db").toString();
QString user = _settings->value("project/mysql/user").toString();
QString pass = _settings->value("project/mysql/pwd").toString();
bool test1 = db_manager.initializeDb(Sql::SQLDbType::Noggit, host, port, db_name, user, pass);
bool test2 = db_manager.testConnectionWithPopup(Sql::SQLDbType::Noggit, true);
// calls MapView::onSettingsSave()
emit saved();
}