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