7#include <QLoggingCategory>
8#include <QJsonDocument>
11#include "RabbitCommonDir.h"
12#include "RabbitCommonTools.h"
16#include "ParameterDatabase.h"
17static Q_LOGGING_CATEGORY(log,
"DB")
20 , m_MinVersion(
"0.1.0")
24 qDebug(log) << Q_FUNC_INFO;
27CDatabase::~CDatabase()
29 qDebug(log) << Q_FUNC_INFO;
36 return SetDatabase(db->GetDatabase(), db->GetParameter());
41 QString szErr =
"Only one of OpenDatabase or SetDatabase can be called, and it can only be called once";
42 Q_ASSERT_X(!IsOpen(),
"Database", szErr.toStdString().c_str());
49QSqlDatabase CDatabase::GetDatabase()
const
59const QString CDatabase::GetError()
const
64void CDatabase::SetError(
const QString &szErr)
70 const QString &szConnectName)
75 bRet = OpenSQLiteDatabase(pPara, szConnectName);
80 QString szErr =
"Only one of OpenDatabase or SetDatabase can be called, and it can only be called once";
81 Q_ASSERT_X(!IsOpen(),
"Database", szErr.toStdString().c_str());
83 if(pPara->GetType() ==
"QSQLITE")
84 bRet = OpenSQLiteDatabase(pPara, szConnectName);
85 else if(pPara->GetType() ==
"QMYSQL")
86 bRet = OpenMySqlDatabase(pPara, szConnectName);
87 else if(pPara->GetType() ==
"QODBC")
88 bRet = OpenODBCDatabase(pPara, szConnectName);
90 SetError(tr(
"Database type not supported:") +
" " + pPara->GetType());
92 QSqlDriver *driver = GetDatabase().driver();
94 qDebug(log) <<
"=== Features for" << pPara->GetType() <<
"===";
95 qDebug(log) <<
"Transactions:" << driver->hasFeature(QSqlDriver::Transactions);
96 qDebug(log) <<
"Query size:" << driver->hasFeature(QSqlDriver::QuerySize);
97 qDebug(log) <<
"BLOB:" << driver->hasFeature(QSqlDriver::BLOB);
98 qDebug(log) <<
"Unicode:" << driver->hasFeature(QSqlDriver::Unicode);
99 qDebug(log) <<
"Prepared queries:" << driver->hasFeature(QSqlDriver::PreparedQueries);
100 qDebug(log) <<
"Named placeholders:" << driver->hasFeature(QSqlDriver::NamedPlaceholders);
101 qDebug(log) <<
"Positional placeholders:" << driver->hasFeature(QSqlDriver::PositionalPlaceholders);
102 qDebug(log) <<
"Last insert ID:" << driver->hasFeature(QSqlDriver::LastInsertId);
103 qDebug(log) <<
"Batch operations:" << driver->hasFeature(QSqlDriver::BatchOperations);
104 qDebug(log) <<
"Event notifications:" << driver->hasFeature(QSqlDriver::EventNotifications);
105 qDebug(log) <<
"Finish query:" << driver->hasFeature(QSqlDriver::FinishQuery);
106 qDebug(log) <<
"Multiple result sets:" << driver->hasFeature(QSqlDriver::MultipleResultSets);
107 qDebug(log) <<
"Cancel query:" << driver->hasFeature(QSqlDriver::CancelQuery);
114 const QString &szConnectionName)
118 szFile = pPara->GetDatabaseName();
120 return OpenSQLiteDatabase(szFile, szConnectionName);
123bool CDatabase::OpenSQLiteDatabase(
124 const QString& szFile,
const QString& szConnectionName)
126 QString databasePath = szFile;
127 if (databasePath.isEmpty()) {
129 QString dataDir = RabbitCommon::CDir::Instance()->GetDirUserDatabase();
135 databasePath = dir.filePath(
"remote_control_dev.db");
137 databasePath = dir.filePath(
"remote_control.db");
141 if(!szConnectionName.isEmpty())
142 m_szConnectName = szConnectionName;
145 m_database = QSqlDatabase::addDatabase(
"QSQLITE", m_szConnectName);
146 m_database.setDatabaseName(databasePath);
148 if (!m_database.open()) {
149 SetError(
"Failed to open sqlite database: " + m_database.databaseName()
150 +
"; Connect name: " + m_database.connectionName()
151 +
"; Error: " + m_database.lastError().text());
152 qCritical(log) << GetError();
157 qInfo(log) <<
"Open sqlite database:" << m_database.databaseName()
158 <<
"Connect name:" << m_database.connectionName();
164 const QString &szConnectName)
166 bool success =
false;
167 if(!pPara)
return false;
169 if(!szConnectName.isEmpty())
170 m_szConnectName = szConnectName;
173 m_database = QSqlDatabase::addDatabase(
"QMYSQL", m_szConnectName);
174 QString szDbName = pPara->GetDatabaseName();
175 if(szDbName.isEmpty()) {
177 szDbName =
"remote_control_dev";
179 szDbName =
"remote_control";
183 auto &net = pPara->m_Net;
184 m_database.setHostName(net.GetHost());
185 m_database.setPort(net.GetPort());
186 auto &user = net.m_User;
187 m_database.setUserName(user.GetName());
188 m_database.setPassword(user.GetPassword());
190 if (!m_database.open()) {
191 SetError(
"Failed to open mysql database: " + m_database.databaseName()
192 +
"; Connect name: " + m_database.connectionName()
193 +
"; Host: " + net.GetHost()
194 +
"; Port: " + QString::number(net.GetPort())
195 +
"; User: " + user.GetUser()
196 +
"; Error: " + m_database.lastError().text()
198 qCritical(log) << GetError();
204 QSqlQuery query(GetDatabase());
205 success = query.exec(
"CREATE DATABASE IF NOT EXISTS " + szDbName);
207 SetError(
"Failed to create mysql database: " + szDbName
208 +
"; Error: " + query.lastError().text()
209 +
"; Sql: " + query.executedQuery());
210 qCritical(log) << GetError();
214 success = query.exec(
"use " + szDbName);
216 SetError(
"Failed to use " + szDbName
217 +
"; Error: " + query.lastError().text()
218 +
"; Sql: " + query.executedQuery());
219 qCritical(log) << GetError();
223 m_database.setDatabaseName(szDbName);
224 qInfo(log) <<
"Open mysql database:" << m_database.databaseName()
225 <<
"Connect name:" << m_database.connectionName()
226 <<
"Host:" << net.GetHost() <<
"Port:" << net.GetPort()
227 <<
"User:" << user.GetUser();
233 const QString &szConnectName)
235 if(!pPara)
return false;
237 if(!szConnectName.isEmpty())
238 m_szConnectName = szConnectName;
241 m_database = QSqlDatabase::addDatabase(
"QODBC", m_szConnectName);
242 QString szDbName = pPara->GetDatabaseName();
243 if(szDbName.isEmpty()) {
245 szDbName =
"remote_control_dev";
247 szDbName =
"remote_control";
250 m_database.setDatabaseName(szDbName);
252 if (!m_database.open()) {
253 SetError(
"Failed to open odbc database: " + m_database.databaseName()
254 +
"; Connect name: " + m_database.connectionName()
255 +
"; Error: " + m_database.lastError().text()
257 qCritical(log) << GetError();
263 QSqlQuery query(GetDatabase());
264 bool success = query.exec(
"CREATE DATABASE IF NOT EXISTS " + szDbName);
266 SetError(
"Failed to create database: " + szDbName
267 +
"; Error: " + query.lastError().text()
268 +
"; Sql: " + query.executedQuery());
269 qCritical(log) << GetError();
273 success = query.exec(
"use " + szDbName);
275 SetError(
"Failed to use " + szDbName
276 +
"; Error: " + query.lastError().text()
277 +
"; Sql: " + query.executedQuery());
278 qCritical(log) << GetError();
282 qInfo(log) <<
"Open odbc database:" << m_database.databaseName()
283 <<
"Connect name:" << m_database.connectionName();
292 bRet = OnInitializeSqliteDatabase();
295 if(m_pPara->GetType() ==
"QSQLITE")
296 bRet = OnInitializeSqliteDatabase();
297 else if(m_pPara->GetType() ==
"QMYSQL" || m_pPara->GetType() ==
"QODBC") {
298 bRet = OnInitializeMySqlDatabase();
300 SetError(
"Don't support:" + m_pPara->GetType());
301 qWarning(log) << GetError();
306bool CDatabase::OnInitializeSqliteDatabase()
311bool CDatabase::OnInitializeMySqlDatabase()
316bool CDatabase::IsOpen()
const
318 return m_database.isOpen();
324 QString szErr =
"This instance is not the owner of the database, but it is will close the database.";
325 qWarning(log) << szErr;
326 Q_ASSERT_X(m_bOwner,
"CDatabase", szErr.toStdString().c_str());
328 if(m_database.isOpen()) {
331 QSqlDatabase::removeDatabase(m_szConnectName);
334bool CDatabase::ExportToJsonFile(
const QString &szFile)
338 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
339 SetError(
"Failed to open export JSON file: " + szFile +
"; Error: " + file.errorString());
340 qCritical(log) << GetError();
344 QTextStream out(&file);
345#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
346 out.setEncoding(QStringConverter::Utf8);
348 out.setCodec(
"UTF-8");
350 out.setGenerateByteOrderMark(
true);
354 root.insert(
"Title",
"Rabbit Remote Control");
355 root.insert(
"Author",
"Kang Lin <kl222@126.com>");
356 root.insert(
"Version",
"0.1.0");
359 bRet = ExportToJson(root);
369bool CDatabase::ImportFromJsonFile(
const QString &szFile)
374 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
375 SetError(tr(
"Failed to open import JSON file: %1; Error: %2").arg(szFile, file.errorString()));
376 qCritical(log) << GetError();
382 doc = QJsonDocument::fromJson(file.readAll());
383 if(!doc.isObject()) {
384 SetError(tr(
"Not a valid JSON file"));
385 qCritical(log) << GetError();
388 auto root = doc.object();
389 QString szTitle = root[
"Title"].toString();
390 if(szTitle !=
"Rabbit Remote Control") {
391 SetError(tr(
"File format error. The title: \"%1\" is not \"Rabbit Remote Control\"").arg(szTitle));
392 qCritical(log) << GetError();
395 QString szVersion = root[
"Version"].toString();
396 if(RabbitCommon::CTools::VersionCompare(szVersion, m_MinVersion) < 0) {
397 SetError(tr(
"The version is no longer supported: ")
398 + szVersion +
" < " + m_MinVersion);
399 qCritical(log) << GetError();
402 bRet = ImportFromJson(doc.object());
409bool CDatabase::ImportFromJson(
const QJsonObject &obj)
414bool CDatabase::ExportToJson(QJsonObject &obj)
419CDatabaseIcon::CDatabaseIcon(
const QString &szSuffix, QObject *parent)
422 m_szTableName =
"icon";
423 if(!szSuffix.isEmpty())
424 m_szTableName = m_szTableName +
"_" + szSuffix;
425 m_szConnectName =
"connect_" + m_szTableName;
428bool CDatabaseIcon::OnInitializeSqliteDatabase()
430 QSqlQuery query(GetDatabase());
434 "CREATE TABLE IF NOT EXISTS " + m_szTableName +
" ("
435 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
441 bool success = query.exec(szSql);
443 SetError(
"Failed to create icon sqlite table: " + m_szTableName
444 +
"; Error: " + query.lastError().text()
445 +
"; Sql: " + szSql);
446 qCritical(log) << GetError();
449 szSql =
"CREATE INDEX IF NOT EXISTS idx_" + m_szTableName +
"_name ON " + m_szTableName +
"(name)";
450 success = query.exec(szSql);
452 qWarning(log) <<
"Failed to create icon name index in" << m_szTableName
453 <<
"Error:" << query.lastError().text()
456 szSql =
"CREATE INDEX IF NOT EXISTS idx_" + m_szTableName +
"_hash ON " + m_szTableName +
"(hash)";
457 success = query.exec(szSql);
459 qWarning(log) <<
"Failed to create icon hash index in" << m_szTableName
460 <<
"Error:" << query.lastError().text()
466bool CDatabaseIcon::OnInitializeMySqlDatabase()
468 QSqlQuery query(GetDatabase());
472 "CREATE TABLE IF NOT EXISTS " + m_szTableName +
" ("
473 " id INTEGER PRIMARY KEY AUTO_INCREMENT,"
477 " UNIQUE KEY idx_icon_name (name(255)),"
478 " INDEX idx_icon_hash (hash(255))"
481 bool success = query.exec(szSql);
483 SetError(
"Failed to create icon mysql table: " + m_szTableName
484 +
"; Error: " + query.lastError().text()
485 +
"; Sql: " + szSql);
486 qCritical(log) << GetError();
496 if(icon.isNull())
return 0;
499 QSqlQuery query(GetDatabase());
500 QString szName = icon.name();
501 if(szName.isEmpty()) {
503 QByteArray data = RabbitCommon::CIconUtils::iconToByteArray(icon);
504 QString szHash = RabbitCommon::CIconUtils::hashIconData(data);
505 if(data.isEmpty() || szHash.isEmpty())
507 szSql =
"SELECT id, data FROM " + m_szTableName +
" WHERE hash=:hash";
508 query.prepare(szSql);
509 query.bindValue(
":hash", szHash);
514 qDebug(log) <<
"Failed to select icon hash:" << szHash
515 <<
"Error:" << query.lastError().text()
516 <<
"Sql:" << query.executedQuery();
519 while (query.next()) {
521 if(data == query.value(1).toByteArray()) {
522 return query.value(0).toInt();
526 szSql =
"INSERT INTO " + m_szTableName +
" (hash, data) "
527 "VALUES (:hash, :data)";
528 query.prepare(szSql);
529 query.bindValue(
":hash", szHash);
530 query.bindValue(
":data", data);
533 SetError(
"Failed to insert icon hash: " + szHash
534 +
"; Error: " + query.lastError().text()
535 +
"; Sql: " + query.executedQuery());
536 qCritical(log) << GetError();
539 return query.lastInsertId().toInt();
543 szSql =
"SELECT id FROM " + m_szTableName +
" WHERE name=:name";
544 query.prepare(szSql);
545 query.bindValue(
":name", szName);
548 SetError(
"Failed to select icon name: " + szName
549 +
"; Error: " + query.lastError().text()
550 +
"; Sql: " + query.executedQuery());
551 qCritical(log) << GetError();
555 return query.value(0).toInt();
559 szSql =
"INSERT INTO " + m_szTableName +
" (name) VALUES (:name)";
560 query.prepare(szSql);
561 query.bindValue(
":name", szName);
564 SetError(
"Failed to insert icon name: " + szName
565 +
"; Error: " + query.lastError().text()
566 +
"; Sql: " + query.executedQuery());
567 qCritical(log) << GetError();
570 return query.lastInsertId().toInt();
576 QSqlQuery query(GetDatabase());
578 "SELECT name, data FROM " + m_szTableName +
581 query.bindValue(
":id",
id);
582 bool bRet = query.exec();
584 SetError(
"Failed to get icon id: " + QString::number(
id)
585 +
"; Error: " + query.lastError().text()
586 +
"; Sql: " + query.executedQuery());
587 qCritical(log) << GetError();
592 QString szName = query.value(0).toString();
593 if(!szName.isEmpty()) {
594 return QIcon::fromTheme(szName);
596 QByteArray ba = query.value(1).toByteArray();
597 return RabbitCommon::CIconUtils::byteArrayToIcon(ba);
630bool CDatabaseIcon::ExportIconToJson(
const QIcon &icon, QJsonObject &obj)
632 QString szIconName = icon.name();
633 if(szIconName.isEmpty()) {
634 QByteArray baIcon = RabbitCommon::CIconUtils::iconToByteArray(icon);
635 obj.insert(
"IconData", baIcon.toBase64().toStdString().c_str());
637 obj.insert(
"IconName", szIconName);
642bool CDatabaseIcon::ImportIconFromJson(
const QJsonObject &itemObj, QIcon &icon)
644 QString szIconName = itemObj[
"IconName"].toString();
645 if(szIconName.isEmpty()) {
646 QByteArray baIcon(itemObj[
"IconData"].toString().toStdString().c_str());
647 if(!baIcon.isEmpty()) {
648 baIcon = QByteArray::fromBase64(baIcon);
649 icon = RabbitCommon::CIconUtils::byteArrayToIcon(baIcon);
652 icon = QIcon::fromTheme(szIconName);
660 QFileInfo fi(szFile);
661 if(fi.isRelative()) {
662 fi = QFileInfo(
SetFile(szFile));
665 szErr =
"File is not exist: " + fi.filePath();
666 qCritical(log) << szErr;
669 QFile f(fi.absoluteFilePath());
670 if(!f.open(QFile::ReadOnly | QFile::Text)) {
671 szErr =
"Failed to open file: " + f.fileName() +
"; Error: " + f.errorString();
672 qCritical(log) << szErr;
675 QString szFileContent = f.readAll();
677 if(szFileContent.isEmpty()) {
678 qCritical(log) <<
"The file is empty:" << szFile;
681 obj.insert(
"FileName", fi.fileName());
682 obj.insert(
"FileContent", szFileContent);
689 QString szFileContent = obj[
"FileContent"].toString();
690 if(szFileContent.isEmpty()) {
691 qCritical(log) <<
"The file content is empty.";
694 szFile = obj[
"FileName"].toString();
695 if(szFile.isEmpty()) {
696 qCritical(log) <<
"The file name is empty.";
700 QFileInfo fi(szFile);
703 if(!f.open(QFile::WriteOnly | QFile::Text)) {
704 szErr =
"Failed to open file: " + szFile
705 +
"; Error: " + f.errorString();
706 qCritical(log) << szErr;
709 f.write(szFileContent.toStdString().c_str(), szFileContent.size());
718 if(!bRet)
return bRet;
723CDatabaseFile::CDatabaseFile(
const QString &szSuffix, QObject *parent)
726 m_szTableName =
"file";
727 if(!szSuffix.isEmpty())
728 m_szTableName = m_szTableName +
"_" + szSuffix;
729 m_szConnectName =
"connect_" + m_szTableName;
735 if(szFile.isEmpty())
return content;
736 QFileInfo fi(szFile);
737 QSqlQuery query(GetDatabase());
739 "SELECT content FROM " + m_szTableName +
" "
740 " WHERE file=:file");
741 query.bindValue(
":file", fi.fileName());
742 bool ok = query.exec();
745 content = query.value(0).toByteArray();
748 SetError(
"Failed to Load file from: " + m_szTableName
749 +
"; Error: " + query.lastError().text()
750 +
"; Sql: " + query.executedQuery());
751 qCritical(log) << GetError();
759 if(szFile.isEmpty())
return false;
761 if(!f.open(QFile::ReadOnly | QFile::Text)) {
762 SetError(
"Failed to open file: " + szFile
763 +
"; Error: " + f.errorString());
766 QByteArray content = f.readAll();
768 QFileInfo fi(szFile);
769 QSqlQuery query(GetDatabase());
770 bRet = query.prepare(
771 "SELECT content FROM " + m_szTableName +
" "
775 SetError(
"Failed to prepare: " + query.executedQuery()
776 +
"; Error: " + query.lastError().text());
777 qCritical(log) << GetError();
780 query.bindValue(
":file", fi.fileName());
783 SetError(
"Failed to exec: " + query.executedQuery()
784 +
"; Error: " + query.lastError().text());
785 qCritical(log) << GetError();
790 "UPDATE " + m_szTableName +
" "
791 "SET content = :content "
794 query.bindValue(
":file", fi.fileName());
795 query.bindValue(
":content", content);
798 "INSERT INTO " + m_szTableName +
" "
800 "VALUES (:file, :content)"
802 query.bindValue(
":file", fi.fileName());
803 query.bindValue(
":content", content);
807 SetError(
"Failed to save file to:" + m_szTableName
808 +
"; Error: " + query.lastError().text()
809 +
"; Sql: " + query.executedQuery());
810 qCritical(log) << GetError();
816bool CDatabaseFile::OnInitializeSqliteDatabase()
818 QSqlQuery query(GetDatabase());
822 "CREATE TABLE IF NOT EXISTS " + m_szTableName +
" ("
823 " file TEXT KEY NOT NULL UNIQUE,"
827 bool success = query.exec(szSql);
829 SetError(
"Failed to create file sqlite table: " + m_szTableName
830 +
"; Error: " + query.lastError().text()
831 +
"; Sql: " + query.executedQuery());
832 qCritical(log) << GetError();
839bool CDatabaseFile::OnInitializeMySqlDatabase()
841 QSqlQuery query(GetDatabase());
842 QString szSql =
"CREATE TABLE IF NOT EXISTS `" + m_szTableName +
"` ( "
843 "`file` TEXT NOT NULL , "
844 "`content` LONGBLOB, "
845 "UNIQUE KEY `uk_file` (`file`(255))"
847 bool success = query.exec(szSql);
849 SetError(
"Failed to create file mysql table: " + m_szTableName
850 +
"; Error: " + query.lastError().text()
851 +
"; Sql: " + query.executedQuery());
852 qCritical(log) << GetError();
859bool CDatabaseFile::IsExist(
const QString &szFile)
861 QSqlQuery query(GetDatabase());
862 bool ok = query.prepare(
"SELECT * from " + m_szTableName +
863 " WHERE `file`=:file");
865 SetError(
"Failed to prepare: " + query.executedQuery()
866 +
"; Error: " + query.lastError().text());
867 qCritical(log) << GetError();
870 query.bindValue(
":file", szFile);
873 SetError(
"Failed to exec: " + query.executedQuery()
874 +
"; Error: " + query.lastError().text());
875 qCritical(log) << GetError();
883 QFileInfo fi(szFile);
885 if(fi.isRelative()) {
886 return RabbitCommon::CDir::Instance()->GetDirUserData()
887 + QDir::separator() + szFile;
896 QFileInfo d(RabbitCommon::CDir::Instance()->GetDirUserData() + QDir::separator());
897 if(fi.absolutePath() == d.absolutePath())
898 szFile = fi.fileName();
bool ImportFileToDatabaseFromJson(const QJsonObject &obj, QString &szFile)
Import file to database from JSON
static bool ExportFileToJson(const QString &szFile, QJsonObject &obj)
ExportFileToJson
static bool ImportFileFromJson(const QJsonObject &obj, QString &szFile)
Import file from JSON
bool Save(const QString &szFile)
Save
QByteArray Load(const QString &szFile)
Load
static QString SetFile(const QString &szFile)
Set the file with file system to the file in database
static QString GetFile(const QString &szFile)
Get the file with file system from the file in database
int GetIcon(const QIcon &icon)
Get icon id
void CloseDatabase()
Close database
virtual bool OnInitializeDatabase()
Initialize database
bool SetDatabase(const CDatabase *db)
Share an existing database
bool OpenDatabase(const CParameterDatabase *pPara=nullptr, const QString &szConnectName=QString())
Open a new database