玉兔远程控制 0.1.0-bate8
载入中...
搜索中...
未找到
FavoriteDatabase.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <stdexcept>
4#include <QJsonDocument>
5#include <QJsonObject>
6#include <QJsonArray>
7#include <QFile>
8#include <QFileInfo>
9#include <QSqlQuery>
10#include <QSqlError>
11#include <QLoggingCategory>
12#include "FavoriteDatabase.h"
13#include "IconUtils.h"
14
15static Q_LOGGING_CATEGORY(log, "App.Favorite.Db")
17 : CDatabaseTree{"favorite", parent}
18{
19 qDebug(log) << Q_FUNC_INFO;
20}
21
22bool CFavoriteDatabase::OnInitializeSqliteDatabase()
23{
24 QSqlQuery query(GetDatabase());
25
26 // Create favorite table
27 QString szSql =
28 "CREATE TABLE IF NOT EXISTS favorite ("
29 " id INTEGER PRIMARY KEY AUTOINCREMENT," // the id is the key of tree table
30 " name TEXT NOT NULL,"
31 " icon INTEGER DEFAULT 0,"
32 " file TEXT UNIQUE NOT NULL,"
33 " description TEXT"
34 ")";
35 bool success = query.exec(szSql);
36
37 if (!success) {
38 SetError("Failed to create favorite table: " + query.lastError().text()
39 + "; Sql: " + szSql);
40 qCritical(log) << GetError();
41 return false;
42 }
43
44 // Create index
45 success = query.exec("CREATE INDEX IF NOT EXISTS idx_favorite_file ON favorite(file)");
46 if (!success) {
47 qWarning(log) << "Failed to create idx_favorite_file." << query.lastError().text();
48 }
49
50 // Drop trigger if exists
51 if (!query.exec("DROP TRIGGER IF EXISTS delete_icon_after_favorite")) {
52 qDebug(log) << "Failed to drop trigger delete_icon_after_favorite:" << query.lastError().text();
53 }
54 // Create trigger
55 szSql = R"(
56 CREATE TRIGGER delete_icon_after_favorite
57 AFTER DELETE ON favorite
58 FOR EACH ROW
59 BEGIN
60 DELETE FROM icon
61 WHERE id = OLD.icon
62 AND OLD.icon != 0
63 AND NOT EXISTS (
64 SELECT 1 FROM favorite WHERE icon = OLD.icon
65 )
66 AND NOT EXISTS (
67 SELECT 1 FROM recent WHERE icon = OLD.icon
68 );
69 END;
70 )";
71 success = query.exec(szSql);
72 if (!success) {
73 qWarning(log) << "Failed to create trigger delete_icon_after_favorite." << query.lastError().text();
74 }
75 success = CDatabaseTree::OnInitializeSqliteDatabase();
76 return success;
77}
78
79bool CFavoriteDatabase::OnInitializeMySqlDatabase()
80{
81 bool success = false;
82 QSqlQuery query(GetDatabase());
83
84 // Create favorite table
85 QString szSql =
86 "CREATE TABLE IF NOT EXISTS favorite ("
87 " id INTEGER PRIMARY KEY AUTO_INCREMENT," // the id is the key of tree table
88 " name TEXT NOT NULL,"
89 " icon INTEGER DEFAULT 0,"
90 " file TEXT NOT NULL,"
91 " description TEXT,"
92 " UNIQUE KEY uk_favorite_file (file(255))"
93 ")";
94 success = query.exec(szSql);
95 if (!success) {
96 SetError("Failed to create favorite table: "
97 + query.lastError().text()
98 + "; Sql: " + szSql);
99 qCritical(log) << GetError();
100 return false;
101 }
102
103 // Drop trigger if exists
104 if (!query.exec("DROP TRIGGER IF EXISTS delete_icon_after_favorite")) {
105 qDebug(log) << "Failed to drop trigger delete_icon_after_favorite:"
106 << query.lastError().text()
107 << "Sql:" << query.executedQuery();
108 }
109 // Create trigger
110 szSql = R"(
111 CREATE TRIGGER delete_icon_after_favorite
112 AFTER DELETE ON favorite
113 FOR EACH ROW
114 BEGIN
115 DECLARE favorite_count INT DEFAULT 0;
116 DECLARE recent_count INT DEFAULT 0;
117
118 IF OLD.icon != 0 THEN
119 -- 统计favorite表中引用该icon的数量
120 SELECT COUNT(*) INTO favorite_count
121 FROM favorite
122 WHERE icon = OLD.icon;
123
124 -- 统计recent表中引用该icon的数量
125 SELECT COUNT(*) INTO recent_count
126 FROM recent
127 WHERE icon = OLD.icon;
128
129 -- 如果都没有引用,则删除icon
130 IF favorite_count = 0 AND recent_count = 0 THEN
131 DELETE FROM icon WHERE id = OLD.icon;
132 END IF;
133 END IF;
134 END
135 )";
136 success = query.exec(szSql);
137 if (!success) {
138 qWarning(log) << "Failed to create trigger delete_icon_after_favorite."
139 << query.lastError().text()
140 << "Sql:" << query.executedQuery();
141 }
142
143 return CDatabaseTree::OnInitializeMySqlDatabase();
144}
145
147{
148 bool bRet = false;
150 if(!bRet) return false;
151 bRet = m_IconDB.SetDatabase(GetDatabase(), m_pPara);
152 if(!bRet) return false;
153 bRet = m_FileDB.SetDatabase(GetDatabase(), m_pPara);
154 return bRet;
155}
156
157int CFavoriteDatabase::AddFavorite(const QString &szFile,
158 const QString &szName, const QIcon &icon,
159 const QString szDescription, int parentId)
160{
161 int ret = 0;
162 if (szName.trimmed().isEmpty()) {
163 qWarning(log) << "Favorite name cannot be empty";
164 return ret;
165 }
166
167 if (szFile.trimmed().isEmpty()) {
168 qCritical(log) << "Favorite file cannot be empty";
169 return ret;
170 }
171
172 // 验证 parentId 是否存在(如果parentId不为0)
173 if (parentId > 0) {
174 if (GetNode(parentId).GetId() == 0) {
175 qWarning(log) << "Parent item with ID" << parentId << "does not exist";
176 return ret;
177 }
178 }
179
180 QSqlDatabase db = GetDatabase();
181 bool success = false;
182 // 使用事务确保数据一致性
183 if (!db.transaction()) {
184 SetError("Failed to start transaction: " + db.lastError().text());
185 qCritical(log) << GetError();
186 return ret;
187 }
188
189 try {
190 QSqlQuery query(db);
191 int key = 0;
192 //检查是否已存在
193 query.prepare(
194 "SELECT id FROM favorite "
195 "WHERE file=:file");
196 query.bindValue(":file", szFile);
197 if(query.exec() && query.next()) {
198 key = query.value(0).toInt();
199 }
200
201 if(0 == key) {
202 // 插入新记录
203 query.prepare(
204 "INSERT INTO favorite (name, icon, file, description)"
205 "VALUES (:name, :icon, :file, :description)"
206 );
207 query.bindValue(":name", szName);
208 query.bindValue(":icon", m_IconDB.GetIcon(icon));
209 query.bindValue(":file", szFile);
210 query.bindValue(":description", szDescription);
211
212 success = query.exec();
213 if (!success) {
214 QString szErr = "Failed to insert favorite table: " + query.lastError().text();
215 SetError(szErr);
216 throw std::runtime_error(szErr.toStdString());
217 }
218
219 key = query.lastInsertId().toInt();
220 if(0 >= key) {
221 QString szErr = "Failed to insert favorite table";
222 SetError(szErr);
223 throw std::runtime_error(szErr.toStdString());
224 }
225 }
226
227 // 在 tree 表中增加
228 TreeItem item(TreeItem::Leaf);
229 item.SetKey(key);
230 item.SetParentId(parentId);
231 int id = Add(item);
232 if (0 >= id) {
233 QString szErr = "Failed to insert favorite folder table";
234 SetError(szErr);
235 throw std::runtime_error(szErr.toStdString());
236 }
237
238 // 提交事务
239 if (!db.commit()) {
240 QString szErr = "Failed to commit transaction:" + db.lastError().text();
241 SetError(szErr);
242 throw std::runtime_error(szErr.toStdString());
243 }
244
245 qDebug(log) << "Successfully added favorite:" << szName << "ID:" << key << "Parent:" << parentId;
246 ret = id;
247
248 } catch (const std::exception &e) {
249 qCritical(log) << "Exception in AddFavorite:" << e.what();
250 db.rollback();
251 } catch (...) {
252 qCritical(log) << "Unknown exception in AddFavorite";
253 db.rollback();
254 }
255
256 return ret;
257}
258
259bool CFavoriteDatabase::UpdateFavorite(
260 int id, const QString& szName,
261 const QIcon &icon, const QString szDescription)
262{
263 QSqlQuery query(GetDatabase());
264 QString szSql;
265 if(!szName.isEmpty())
266 szSql += "name=\"" + szName + "\"";
267 if(!icon.isNull()) {
268 if(!szSql.isEmpty())
269 szSql += ", ";
270 szSql += "icon=" + QString::number(m_IconDB.GetIcon(icon));
271 }
272 if(!szDescription.isEmpty()) {
273 if(!szSql.isEmpty())
274 szSql += ", ";
275 szSql += "description=\"" + szDescription + "\"";
276 }
277
278 szSql = "UPDATE favorite SET " + szSql + " WHERE id=" + QString::number(id);
279 //qDebug(log) << "Sql:" << szSql;
280 bool ok = query.exec(szSql);
281 if(!ok) {
282 SetError("Failed to update favorite: " + query.lastError().text()
283 + "; Sql: " + szSql);
284 qCritical(log) << GetError();
285 }
286 return ok;
287}
288
289bool CFavoriteDatabase::UpdateFavorite(
290 const QString &szFile, const QString &szName,
291 const QIcon &icon, const QString szDescription)
292{
293 QSqlQuery query(GetDatabase());
294 QString szSql;
295 if(!szName.isEmpty())
296 szSql += "name=\"" + szName + "\"";
297 if(!icon.isNull()) {
298 if(!szSql.isEmpty())
299 szSql += ", ";
300 szSql += "icon=" + QString::number(m_IconDB.GetIcon(icon));
301 }
302 if(!szDescription.isEmpty()) {
303 if(!szSql.isEmpty())
304 szSql += ", ";
305 szSql += "description=\"" + szDescription + "\"";
306 }
307
308 szSql = "UPDATE favorite SET " + szSql + " WHERE file=\"" + szFile + "\"";
309 //qDebug(log) << "Sql:" << szSql;
310 bool ok = query.exec(szSql);
311 if(!ok) {
312 SetError("Failed to update favorite: " + query.lastError().text()
313 + "; Sql: " + szSql);
314 qCritical(log) << GetError();
315 }
316 return ok;
317}
318
319CFavoriteDatabase::Item CFavoriteDatabase::GetFavorite(int id)
320{
321 Item item(TreeItem::Leaf);
322 auto leaf = GetLeaf(id);
323 if(leaf.GetId() == 0)
324 return item;
325
326 QSqlQuery query(GetDatabase());
327 query.prepare(
328 "SELECT id, name, icon, file, description FROM favorite "
329 "WHERE id = :id"
330 );
331 query.bindValue(":id", leaf.GetKey());
332 //qDebug(log) << "SQL:" << query.executedQuery();
333 //qDebug(log) << "Bound value:" << query.boundValues();
334 bool ok = query.exec();
335 if(!ok) {
336 SetError("Failed to get favorite: " + query.lastError().text()
337 + "; Sql: " + query.executedQuery()
338 + "; id: " + QString::number(id));
339 qCritical(log) << GetError();
340 return item;
341 }
342 if(query.next()) {
343 item.id = leaf.GetId();
344 item.parentId = leaf.GetParentId();
345 item.szName = query.value(1).toString();
346 item.icon = m_IconDB.GetIcon(query.value(2).toInt());
347 item.szFile = query.value(3).toString();
348 item.szDescription = query.value(4).toString();
349 item.type = TreeItem::Leaf;
350 }
351 return item;
352}
353
354QList<CFavoriteDatabase::Item> CFavoriteDatabase::GetFavorite(const QString &szFile)
355{
356 QList<CFavoriteDatabase::Item> lstItems;
357 Item item(TreeItem::Leaf);
358
359 QSqlQuery query(GetDatabase());
360 query.prepare(
361 "SELECT id, name, icon, file, description FROM favorite "
362 "WHERE file = :file"
363 );
364 query.bindValue(":file", szFile);
365 //qDebug(log) << "SQL:" << query.executedQuery();
366 //qDebug(log) << "Bound value:" << query.boundValues();
367 bool ok = query.exec();
368 if(!ok) {
369 SetError("Failed to get favorite: " + query.lastError().text()
370 + "; Sql: " + query.executedQuery() + "; szFile: " + szFile);;
371 qCritical(log) << GetError();
372 return lstItems;
373 }
374 if(query.next()) {
375 item.szName = query.value(1).toString();
376 item.icon = m_IconDB.GetIcon(query.value(2).toInt());
377 item.szFile = query.value(3).toString();
378 item.szDescription = query.value(4).toString();
379 item.type = TreeItem::Leaf;
380
381 auto leaf = GetLeavesByKey(query.value(0).toInt());
382 foreach(auto l, leaf) {
383 item.id = l.GetId();
384 item.parentId = l.GetParentId();
385 lstItems << item;
386 }
387 }
388 return lstItems;
389}
390
391CFavoriteDatabase::Item CFavoriteDatabase::GetGroup(int id)
392{
393 auto f = GetNode(id);
394 Item item(f.GetType());
395 item.id = f.GetId();
396 item.parentId = f.GetParentId();
397 item.szName = f.GetName();
398 return item;
399}
400
401QList<CFavoriteDatabase::Item> CFavoriteDatabase::GetChildren(int parentId)
402{
403 QList<Item> children;
404 // Get nodes
405 auto folders = GetSubNodes(parentId);
406 foreach(auto f, folders) {
407 Item item(f.GetType());
408 item.id = f.GetId();
409 item.parentId = f.GetParentId();
410 item.szName = f.GetName();
411 children << item;
412 }
413 // Get leaves
414 auto leaves = GetLeaves(parentId);
415 foreach(auto l, leaves) {
416 Item item = GetFavorite(l.GetId());
417 children << item;
418 }
419 return children;
420}
421
423{
424 QSqlQuery query(GetDatabase());
425 query.prepare(
426 "DELETE FROM favorite "
427 "WHERE id = :id"
428 );
429 query.bindValue(":id", key);
430 //qDebug(log) << "SQL:" << query.executedQuery();
431 //qDebug(log) << "Bound value:" << query.boundValues();
432 bool ok = query.exec();
433 if(!ok) {
434 SetError("Failed to delete favorite: " + query.lastError().text()
435 + "; Sql: " + query.executedQuery()
436 + "; key: " + QString::number(key));
437 qCritical(log) << GetError();
438 return false;
439 }
440 return ok;
441}
442
443bool CFavoriteDatabase::ExportToJson(QJsonObject &obj)
444{
445 bool bRet = true;
446 QJsonArray root;
447 bRet = ExportToJson(0, root);
448 if(bRet)
449 obj.insert("favorite", root);
450 return bRet;
451}
452
453bool CFavoriteDatabase::ImportFromJson(const QJsonObject &obj)
454{
455 QJsonArray favorites = obj["favorite"].toArray();
456 if(favorites.isEmpty()) {
457 SetError(tr("The file format is error. Json without \"favorite\""));
458 qCritical(log) << GetError();
459 return false;
460 }
461
462 return ImportFromJson(0, favorites);
463}
464
465bool CFavoriteDatabase::ImportFromJson(int parentId, const QJsonArray &obj)
466{
467 for(auto it = obj.begin(); it != obj.end(); it++) {
468 QJsonObject itemObj = it->toObject();
469 if(itemObj.isEmpty()) continue;
470 if(TreeItem::Node == itemObj["type"].toInt()) {
471 QString szName = itemObj["name"].toString();
472 int id = AddNode(szName, parentId);
473 QJsonArray items = itemObj[szName].toArray();
474 if(items.isEmpty()) continue;
475 ImportFromJson(id, items);
476 continue;
477 }
478
479 QString szFile;
480 bool bRet = m_FileDB.ImportFileToDatabaseFromJson(itemObj, szFile);
481 if(!bRet) continue;
482 QIcon icon;
483 bRet = CDatabaseIcon::ImportIconFromJson(itemObj, icon);
484 if(!bRet) continue;
485
486 AddFavorite(CDatabaseFile::SetFile(szFile), itemObj["name"].toString(), icon, itemObj["description"].toString(), parentId);
487 }
488
489 return true;
490}
491
492bool CFavoriteDatabase::ExportToJson(int parentId, QJsonArray &obj)
493{
494 auto items = GetChildren(parentId);
495 foreach(auto item, items) {
496 QJsonObject oItem;
497 if(item.isFolder()) {
498 QJsonArray aItem;
499 bool bRet = ExportToJson(item.id, aItem);
500 if(!bRet) continue;
501
502 oItem.insert("type", TreeItem::Node);
503 oItem.insert("name", item.szName);
504 oItem.insert(item.szName, aItem);
505
506 } else {
507 oItem.insert("type", item.type);
508 oItem.insert("name", item.szName);
509
510 // File
511 bool bRet = CDatabaseFile::ExportFileToJson(CDatabaseFile::GetFile(item.szFile), oItem);
512 if(!bRet) continue;
513
514 // Icon
515 bRet = CDatabaseIcon::ExportIconToJson(item.GetIcon(), oItem);
516 if(!bRet) continue;
517
518 oItem.insert("description", item.szDescription);
519 }
520
521 obj.append(oItem);
522
523 }
524 return true;
525}
bool ImportFileToDatabaseFromJson(const QJsonObject &obj, QString &szFile)
Import file to database from JSON
Definition Database.cpp:715
static bool ExportFileToJson(const QString &szFile, QJsonObject &obj)
ExportFileToJson
Definition Database.cpp:657
static QString SetFile(const QString &szFile)
Set the file with file system to the file in database
Definition Database.cpp:892
static QString GetFile(const QString &szFile)
Get the file with file system from the file in database
Definition Database.cpp:881
int GetIcon(const QIcon &icon)
Get icon id
Definition Database.cpp:493
The CDatabaseTree class
virtual int Add(const TreeItem &item)
Add item
QList< TreeItem > GetLeavesByKey(int key)
Get leaves
QList< TreeItem > GetLeaves(int nodeId)
Get the leaves under nodeId
virtual bool OnInitializeDatabase() override
Initialize database
bool SetDatabase(const CDatabase *db)
Share an existing database
Definition Database.cpp:34
The CFavoriteDatabase class
bool OnInitializeDatabase() override
Initialize database
virtual bool OnDeleteKey(int key) override
从 key 相关的表中删除 key