5#include <QStandardPaths>
8#include <QLoggingCategory>
9#include <QCoreApplication>
10#include <QJsonDocument>
13#include "HistoryDatabase.h"
15static Q_LOGGING_CATEGORY(log,
"WebBrowser.History.DB")
20static void sqlTrace(
void* ,
const char* sql) {
21 qDebug(log) <<
"SQL Trace:" << sql;
25void enableSqlTrace(
const QString& connectionName = QSqlDatabase::defaultConnection) {
26 QSqlDatabase db = QSqlDatabase::database(connectionName);
29 QVariant v = db.driver()->handle();
30 if (v.isValid() && qstrcmp(v.typeName(),
"sqlite3*") == 0) {
31 sqlite3* handle = *
static_cast<sqlite3**
>(v.data());
33 sqlite3_trace(handle, sqlTrace,
nullptr);
34 qDebug(log) <<
"SQLite trace enabled";
39void enableSqlTrace(
const QString& connectionName)
44CHistoryDatabase::CHistoryDatabase(QObject *parent)
47 qDebug(log) << Q_FUNC_INFO;
50CHistoryDatabase::~CHistoryDatabase()
52 qDebug(log) << Q_FUNC_INFO;
59 if(!success)
return false;
60 success = m_UrlDB.
SetDatabase(GetDatabase(), m_pPara);
64bool CHistoryDatabase::addHistoryEntry(
const QString &url)
66 if (url.isEmpty())
return false;
70 nUrlId = m_UrlDB.GetId(url);
72 nUrlId = m_UrlDB.AddUrl(url);
78 QSqlQuery query(GetDatabase());
80 "INSERT INTO history (url, visit_time) "
81 "VALUES (:url, :visit_time) "
83 query.bindValue(
":url", nUrlId);
84 QDateTime tm = QDateTime::currentDateTime();
85 query.bindValue(
":visit_time", tm);
87 bool success = query.exec();
89 QString szErr =
"Failed to add history: " + query.lastError().text()
90 +
"; Sql: " + query.executedQuery()
93 qCritical(log) << GetError();
99bool CHistoryDatabase::addHistoryEntry(
const QString &url,
const QString& title,
const QDateTime& time)
101 if (url.isEmpty())
return false;
105 nUrlId = m_UrlDB.GetId(url);
107 nUrlId = m_UrlDB.AddUrl(url, title);
113 QSqlQuery query(GetDatabase());
115 "INSERT INTO history (url, visit_time) "
116 "VALUES (:url, :visit_time) "
118 query.bindValue(
":url", nUrlId);
119 query.bindValue(
":visit_time", time);
121 bool success = query.exec();
123 QString szErr =
"Failed to add history: " + query.lastError().text()
124 +
"; Sql: " + query.executedQuery()
127 qCritical(log) << GetError();
133bool CHistoryDatabase::updateHistoryEntry(
const QString& url,
const QString &title,
const QIcon &icon)
135 if (url.isEmpty())
return false;
138 nUrlId = m_UrlDB.GetId(url);
140 nUrlId = m_UrlDB.AddUrl(url, title, icon);
143 return m_UrlDB.UpdateUrl(url, title, icon);
146bool CHistoryDatabase::updateHistoryEntry(
int id,
const QString &title,
const QIcon &icon)
148 QSqlQuery query(GetDatabase());
154 query.bindValue(
":id",
id);
156 QString szErr =
"Failed to update history: " + query.lastError().text()
157 +
"; Sql: " + query.executedQuery()
158 +
"; id: " + QString::number(
id);
160 qCritical(log) << GetError();
165 int urlId = query.value(0).toInt();
166 return m_UrlDB.UpdateUrl(urlId, title, icon);
172bool CHistoryDatabase::deleteHistoryEntry(
int id)
174 QSqlQuery query(GetDatabase());
175 query.prepare(
"DELETE FROM history WHERE id = :id");
176 query.bindValue(
":id",
id);
178 bool bRet = query.exec();
180 QString szErr =
"Failed to delete history: " + query.lastError().text()
181 +
"; Sql: " + query.executedQuery()
182 +
"; id: " + QString::number(
id);
184 qCritical(log) << GetError();
189bool CHistoryDatabase::deleteHistoryEntry(
const QString &url)
191 int urlId = m_UrlDB.GetId(url);
194 QSqlQuery query(GetDatabase());
195 query.prepare(
"DELETE FROM history WHERE url = :url");
196 query.bindValue(
":url", urlId);
197 bool bRet = query.exec();
199 QString szErr =
"Failed to delete history: " + query.lastError().text()
200 +
"; Sql: " + query.executedQuery()
203 qCritical(log) << GetError();
208bool CHistoryDatabase::deleteDomainEntries(
const QString &szDomain)
210 if(szDomain.isEmpty())
return false;
211 auto domains = m_UrlDB.GetDomain(szDomain);
212 foreach(
auto urlId, domains) {
213 QSqlQuery query(GetDatabase());
214 query.prepare(
"DELETE FROM history WHERE url = :url");
215 query.bindValue(
":url", urlId);
217 QString szErr =
"Failed to delete domain: " + szDomain
218 +
"; Error: " + query.lastError().text()
219 +
"; Sql: " + query.executedQuery()
220 +
"; url id: " + QString::number(urlId);
222 qCritical(log) << GetError();
224 m_UrlDB.DeleteUrl(urlId);
229bool CHistoryDatabase::clearHistory(
int days)
231 QSqlQuery query(GetDatabase());
234 QDateTime cutoff = QDateTime::currentDateTime().addDays(-days);
235 query.prepare(
"DELETE FROM history WHERE visit_time < :cutoff");
236 query.bindValue(
":cutoff", cutoff);
238 query.prepare(
"DELETE FROM history");
241 bool success = query.exec();
244 query.exec(
"VACUUM");
246 QString szErr =
"Failed to clear history: " + query.lastError().text()
247 +
"; Sql: " + query.executedQuery();
249 qCritical(log) << GetError();
255void CHistoryDatabase::scheduleCleanup(
int maxDays,
int maxCount)
259 QSqlQuery query(GetDatabase());
260 QDateTime cutoff = QDateTime::currentDateTime().addDays(-maxDays);
261 query.prepare(
"DELETE FROM history WHERE visit_time < :cutoff");
262 query.bindValue(
":cutoff", cutoff);
268 QSqlQuery query(GetDatabase());
270 "DELETE FROM history WHERE id IN ("
271 " SELECT id FROM history "
272 " ORDER BY visit_time DESC "
273 " LIMIT -1 OFFSET :maxCount"
276 query.bindValue(
":maxCount", maxCount);
281QList<HistoryItem> CHistoryDatabase::getAllHistory(
int limit,
int offset)
283 QList<HistoryItem> historyList;
285 QSqlQuery query(GetDatabase());
288 "SELECT id, url, visit_time "
290 "ORDER BY visit_time DESC "
294 "SELECT id, url, visit_time "
296 "ORDER BY visit_time DESC "
297 "LIMIT :limit OFFSET :offset"
299 query.bindValue(
":limit", limit);
300 query.bindValue(
":offset", offset);
303 while (query.next()) {
305 item.id = query.value(0).toInt();
307 item.url = urlItem.szUrl;
308 item.title = urlItem.szTitle;
309 item.visitTime = query.value(2).toDateTime();
310 item.icon = urlItem.icon;
311 historyList.append(item);
314 QString szErr =
"Failed to get all history:" + query.lastError().text()
315 +
"; Sql: " + query.executedQuery();
317 qCritical(log) << GetError();
323QList<HistoryItem> CHistoryDatabase::getHistoryByDate(
const QDate &date)
325 QList<HistoryItem> historyList;
327 QSqlQuery query(GetDatabase());
329 "SELECT id, url, visit_time "
331 "WHERE date(visit_time) = date(:date) "
332 "ORDER BY visit_time DESC"
334 query.bindValue(
":date", date.toString(
"yyyy-MM-dd"));
337 while (query.next()) {
339 item.id = query.value(0).toInt();
341 item.url = urlItem.szUrl;
342 item.title = urlItem.szTitle;
343 item.visitTime = query.value(2).toDateTime();
344 item.icon = urlItem.icon;
345 historyList.append(item);
348 QString szErr =
"Failed to get history by date:" + query.lastError().text()
349 +
"; Sql: " + query.executedQuery();
351 qCritical(log) << GetError();
357QList<HistoryItem> CHistoryDatabase::getHistoryByDate(
358 const QDate &start,
const QDate &end,
int limit)
360 QList<HistoryItem> historyList;
362 QSqlQuery query(GetDatabase());
364 "SELECT id, url, visit_time "
366 "WHERE date(visit_time) >= date(:start) AND date(visit_time) <= date(:end) "
367 "ORDER BY visit_time DESC "
370 query.bindValue(
":start", start.toString(
"yyyy-MM-dd"));
371 query.bindValue(
":end", end.toString(
"yyyy-MM-dd"));
372 query.bindValue(
":limit", limit);
375 while (query.next()) {
377 item.id = query.value(0).toInt();
379 item.url = urlItem.szUrl;
380 item.title = urlItem.szTitle;
381 item.visitTime = query.value(2).toDateTime();
382 item.icon = urlItem.icon;
383 historyList.append(item);
386 QString szErr =
"Failed to get history by date:" + query.lastError().text()
387 +
"; Sql: " + query.executedQuery();
389 qCritical(log) << GetError();
395QList<HistoryItem> CHistoryDatabase::searchHistory(
const QString &keyword)
397 auto items = m_UrlDB.Search(keyword);
398 QList<HistoryItem> retItems;
399 foreach (
auto i, items) {
402 item.title = i.szTitle;
409HistoryItem CHistoryDatabase::getHistoryByUrl(
const QString &url)
413 if(url.isEmpty())
return item;
414 auto urlItem = m_UrlDB.GetItem(url);
418 QSqlQuery query(GetDatabase());
420 "SELECT id, visit_time "
421 "FROM history WHERE url = :url"
423 query.bindValue(
":url", urlItem.id);
427 item.id = query.value(0).toInt();
428 item.url = urlItem.szUrl;
429 item.title = urlItem.szTitle;
430 item.icon = urlItem.icon;
431 item.visitTime = query.value(1).toDateTime();
434 QString szErr =
"Failed to get history by url:" + query.lastError().text()
435 +
"; Sql: " + query.executedQuery()
438 qCritical(log) << GetError();
444HistoryItem CHistoryDatabase::getHistoryById(
int id)
448 QSqlQuery query(GetDatabase());
450 "SELECT id, url, visit_time "
451 "FROM history WHERE id = :id"
453 query.bindValue(
":id",
id);
457 item.id = query.value(0).toInt();
458 int urlId = query.value(1).toInt();
459 auto urlItem = m_UrlDB.GetItem(urlId);
460 item.url = urlItem.szUrl;
461 item.title = urlItem.szTitle;
462 item.icon = urlItem.icon;
463 item.visitTime = query.value(2).toDateTime();
466 QString szErr =
"Failed to get history by id:" + query.lastError().text()
467 +
"; Sql: " + query.executedQuery()
468 +
"; id: " + QString::number(
id);
470 qCritical(log) << GetError();
476int CHistoryDatabase::getHistoryCount()
478 QSqlQuery query(GetDatabase());
479 query.exec(
"SELECT COUNT(*) FROM history");
482 return query.value(0).toInt();
484 QString szErr =
"Failed to get history count:" + query.lastError().text()
485 +
"; Sql: " + query.executedQuery();
487 qCritical(log) << GetError();
493QDateTime CHistoryDatabase::getLastVisitTime()
495 QSqlQuery query(GetDatabase());
496 query.exec(
"SELECT MAX(visit_time) FROM history");
499 return query.value(0).toDateTime();
501 QString szErr =
"Failed to get last visit time:" + query.lastError().text()
502 +
"; Sql: " + query.executedQuery();
504 qCritical(log) << GetError();
510bool CHistoryDatabase::ExportToJson(QJsonObject &obj)
513 auto items = getAllHistory();
514 foreach(
auto it, items) {
516 title.insert(
"title", it.title);
517 title.insert(
"url", it.url);
518 title.insert(
"visit_time", it.visitTime.toString());
521 obj.insert(
"browser_history", list);
525bool CHistoryDatabase::ImportFromJson(
const QJsonObject &obj)
527 auto array = obj[
"browser_history"].toArray();
528 if(array.isEmpty()) {
529 SetError(tr(
"The file format is error. Json without \"browser_history\""));
530 qCritical(log) << GetError();
533 for(
auto it = array.begin(); it != array.end(); it++) {
534 auto o = it->toObject();
535 QString url = o[
"url"].toString();
536 QString title = o[
"title"].toString();
537 QDateTime time = QDateTime::fromString(o[
"visit_time"].toString());
538 qDebug(log) <<
"title:" << title <<
"url:" << url <<
"visit_time:" << time;
539 bool ok = addHistoryEntry(url, title, time);
541 qWarning(log) <<
"Failed to add history:" << o[
"title"].toString();
546bool CHistoryDatabase::OnInitializeSqliteDatabase()
548 QSqlQuery query(GetDatabase());
552 "CREATE TABLE IF NOT EXISTS history ("
553 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
554 " url INTEGER NOT NULL,"
555 " visit_time DATETIME DEFAULT CURRENT_TIMESTAMP"
558 bool success = query.exec();
561 QString szErr =
"Failed to create history sqlite table:"
562 + query.lastError().text()
563 +
"; Sql: " + query.executedQuery();
565 qCritical(log) << GetError();
570 query.prepare(
"CREATE INDEX IF NOT EXISTS idx_history_url ON history(url)");
571 success = query.exec();
573 qWarning(log) <<
"Failed to create index idx_history_url:"
574 << query.lastError().text()
575 << query.executedQuery();
577 query.prepare(
"CREATE INDEX IF NOT EXISTS idx_history_time ON history(visit_time)");
578 success = query.exec();
580 qWarning(log) <<
"Failed to create index idx_history_time:"
581 << query.lastError().text()
582 << query.executedQuery();
588bool CHistoryDatabase::OnInitializeMySqlDatabase()
590 QSqlQuery query(GetDatabase());
592 "CREATE TABLE IF NOT EXISTS history ("
593 " id INTEGER PRIMARY KEY AUTO_INCREMENT,"
594 " url INTEGER NOT NULL,"
595 " visit_time DATETIME DEFAULT CURRENT_TIMESTAMP,"
596 " INDEX idx_history_url (url),"
597 " INDEX idx_history_time (visit_time)"
600 bool success = query.exec();
602 QString szErr =
"Failed to create history mysql table:"
603 + query.lastError().text()
604 +
"; Sql: " + query.executedQuery();
606 qCritical(log) << GetError();
Provide interfaces such as opening the database and initializing the database.
virtual bool OnInitializeDatabase()
Initialize database.
bool SetDatabase(const CDatabase *db)
Share an existing database.
bool OnInitializeDatabase() override
Initialize database.