mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
history: base working implementation
This commit is contained in:
parent
20f2dac755
commit
beed30b156
197
historykeeper.cpp
Normal file
197
historykeeper.cpp
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
This program is libre software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
See the COPYING file for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "historykeeper.h"
|
||||||
|
#include "misc/settings.h"
|
||||||
|
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
static HistoryKeeper *historyInstance = nullptr;
|
||||||
|
|
||||||
|
HistoryKeeper *HistoryKeeper::getInstance()
|
||||||
|
{
|
||||||
|
if (historyInstance == nullptr)
|
||||||
|
{
|
||||||
|
QString path(":memory:");
|
||||||
|
bool encrypted = Settings::getInstance().getEncryptLogs();
|
||||||
|
|
||||||
|
if (Settings::getInstance().getEnableLogging())
|
||||||
|
{
|
||||||
|
path = QDir(Settings::getInstance().getSettingsDirPath()).filePath("qtox_history.sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
path = "/tmp/qtox_history.sqlite"; // just for testing purpose
|
||||||
|
encrypted = false;
|
||||||
|
|
||||||
|
historyInstance = new HistoryKeeper(path, encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return historyInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryKeeper::HistoryKeeper(const QString &path, bool encr)
|
||||||
|
{
|
||||||
|
Q_UNUSED(encr)
|
||||||
|
|
||||||
|
db = QSqlDatabase::addDatabase("QSQLITE");
|
||||||
|
db.setDatabaseName(path);
|
||||||
|
if (!db.open())
|
||||||
|
{
|
||||||
|
qWarning() << QString("Can't open file: %1, history will not be saved!").arg(path);
|
||||||
|
db.setDatabaseName(":memory:");
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.exec(QString("CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, ") +
|
||||||
|
QString("chat_id INTEGER NOT NULL, sender INTERGER NOT NULL, message TEXT NOT NULL);"));
|
||||||
|
db.exec(QString("CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT UNIQUE NOT NULL);"));
|
||||||
|
db.exec(QString("CREATE TABLE IF NOT EXISTS chats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, ctype INTEGER NOT NULL);"));
|
||||||
|
|
||||||
|
updateChatsID();
|
||||||
|
updateAliases();
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryKeeper::~HistoryKeeper()
|
||||||
|
{
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender)
|
||||||
|
{
|
||||||
|
int chat_id = getChatID(chat, ctSingle).first;
|
||||||
|
int sender_id = getAliasID(sender);
|
||||||
|
|
||||||
|
db.exec(QString("INSERT INTO history (chat_id, sender, message) VALUES (%1, %2, '%3');").arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<HistoryKeeper::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to)
|
||||||
|
{
|
||||||
|
QList<HistMessage> res;
|
||||||
|
|
||||||
|
QString timeStr_from = time_from.toString("yyyy-MM-dd hh:mm:ss");
|
||||||
|
QString timeStr_to = time_to.toString("yyyy-MM-dd hh:mm:ss");
|
||||||
|
|
||||||
|
int chat_id = getChatID(chat, ct).first;
|
||||||
|
|
||||||
|
QSqlQuery dbAnswer;
|
||||||
|
if (ct == ctSingle)
|
||||||
|
{
|
||||||
|
dbAnswer = db.exec(QString("SELECT timestamp, user_id, message FROM history INNER JOIN aliases ON history.sender = aliases.id ") +
|
||||||
|
QString("AND timestamp BETWEEN '%1' AND '%2' AND chat_id = %3;").arg(timeStr_from).arg(timeStr_to).arg(chat_id));
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
while (dbAnswer.next())
|
||||||
|
{
|
||||||
|
QString sender = dbAnswer.value(1).toString();
|
||||||
|
QString message = unWrapMessage(dbAnswer.value(2).toString());
|
||||||
|
QDateTime time = dbAnswer.value(0).toDateTime();
|
||||||
|
time.setTimeSpec(Qt::UTC);
|
||||||
|
|
||||||
|
res.push_back({sender,message,time});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HistoryKeeper::wrapMessage(const QString &str)
|
||||||
|
{
|
||||||
|
QString wrappedMessage(str);
|
||||||
|
wrappedMessage.replace("'", "''");
|
||||||
|
return wrappedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HistoryKeeper::unWrapMessage(const QString &str)
|
||||||
|
{
|
||||||
|
QString unWrappedMessage(str);
|
||||||
|
unWrappedMessage.replace("''", "'");
|
||||||
|
return unWrappedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryKeeper::updateChatsID()
|
||||||
|
{
|
||||||
|
auto dbAnswer = db.exec(QString("SELECT * FROM chats;"));
|
||||||
|
|
||||||
|
chats.clear();
|
||||||
|
while (dbAnswer.next())
|
||||||
|
{
|
||||||
|
QString name = dbAnswer.value(1).toString();
|
||||||
|
int id = dbAnswer.value(0).toInt();
|
||||||
|
ChatType ctype = static_cast<ChatType>(dbAnswer.value(2).toInt());
|
||||||
|
|
||||||
|
chats[name] = {id, ctype};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryKeeper::updateAliases()
|
||||||
|
{
|
||||||
|
auto dbAnswer = db.exec(QString("SELECT * FROM aliases;"));
|
||||||
|
|
||||||
|
aliases.clear();
|
||||||
|
while (dbAnswer.next())
|
||||||
|
{
|
||||||
|
QString user_id = dbAnswer.value(1).toString();
|
||||||
|
int id = dbAnswer.value(0).toInt();
|
||||||
|
|
||||||
|
aliases[user_id] = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<int, HistoryKeeper::ChatType> HistoryKeeper::getChatID(const QString &id_str, ChatType ct)
|
||||||
|
{
|
||||||
|
auto it = chats.find(id_str);
|
||||||
|
if (it != chats.end())
|
||||||
|
return it.value();
|
||||||
|
|
||||||
|
db.exec(QString("INSERT INTO chats (name, ctype) VALUES ('%1', '%2');").arg(id_str).arg(ct));
|
||||||
|
updateChatsID();
|
||||||
|
|
||||||
|
return getChatID(id_str, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryKeeper::getAliasID(const QString &id_str)
|
||||||
|
{
|
||||||
|
auto it = aliases.find(id_str);
|
||||||
|
if (it != aliases.end())
|
||||||
|
return it.value();
|
||||||
|
|
||||||
|
db.exec(QString("INSERT INTO aliases (user_id) VALUES ('%1');").arg(id_str));
|
||||||
|
updateAliases();
|
||||||
|
|
||||||
|
return getAliasID(id_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryKeeper::resetInstance()
|
||||||
|
{
|
||||||
|
if (historyInstance == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
delete historyInstance;
|
||||||
|
historyInstance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender)
|
||||||
|
{
|
||||||
|
Q_UNUSED(chat)
|
||||||
|
Q_UNUSED(message)
|
||||||
|
Q_UNUSED(sender)
|
||||||
|
}
|
62
historykeeper.h
Normal file
62
historykeeper.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
This program is libre software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
See the COPYING file for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HISTORYKEEPER_H
|
||||||
|
#define HISTORYKEEPER_H
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QList>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
class HistoryKeeper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct HistMessage
|
||||||
|
{
|
||||||
|
QString sender;
|
||||||
|
QString message;
|
||||||
|
QDateTime timestamp;
|
||||||
|
};
|
||||||
|
enum ChatType {ctSingle = 0, ctGroup};
|
||||||
|
|
||||||
|
static HistoryKeeper* getInstance();
|
||||||
|
static void resetInstance();
|
||||||
|
virtual ~HistoryKeeper();
|
||||||
|
|
||||||
|
void addChatEntry(const QString& chat, const QString& message, const QString& sender);
|
||||||
|
void addGroupChatEntry(const QString& chat, const QString& message, const QString& sender);
|
||||||
|
QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to = QDateTime::currentDateTimeUtc());
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
HistoryKeeper(const QString &path, bool encr = false);
|
||||||
|
HistoryKeeper(HistoryKeeper &settings) = delete;
|
||||||
|
HistoryKeeper& operator=(const HistoryKeeper&) = delete;
|
||||||
|
|
||||||
|
void updateChatsID();
|
||||||
|
void updateAliases();
|
||||||
|
QPair<int, ChatType> getChatID(const QString &id_str, ChatType ct);
|
||||||
|
int getAliasID(const QString &id_str);
|
||||||
|
QString wrapMessage(const QString &str);
|
||||||
|
QString unWrapMessage(const QString &str);
|
||||||
|
|
||||||
|
QSqlDatabase db;
|
||||||
|
QMap<QString, int> aliases;
|
||||||
|
QMap<QString, QPair<int, ChatType>> chats;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HISTORYKEEPER_H
|
4
qtox.pro
4
qtox.pro
|
@ -20,7 +20,7 @@
|
||||||
# See the COPYING file for more details.
|
# See the COPYING file for more details.
|
||||||
|
|
||||||
|
|
||||||
QT += core gui network xml
|
QT += core gui network xml sql
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
TARGET = qtox
|
TARGET = qtox
|
||||||
|
@ -132,6 +132,7 @@ HEADERS += widget/form/addfriendform.h \
|
||||||
widget/tool/chatactions/filetransferaction.h \
|
widget/tool/chatactions/filetransferaction.h \
|
||||||
widget/tool/chatactions/systemmessageaction.h \
|
widget/tool/chatactions/systemmessageaction.h \
|
||||||
widget/tool/chatactions/actionaction.h \
|
widget/tool/chatactions/actionaction.h \
|
||||||
|
historykeeper.h \
|
||||||
widget/maskablepixmapwidget.h
|
widget/maskablepixmapwidget.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -178,4 +179,5 @@ SOURCES += \
|
||||||
widget/tool/chatactions/filetransferaction.cpp \
|
widget/tool/chatactions/filetransferaction.cpp \
|
||||||
widget/tool/chatactions/systemmessageaction.cpp \
|
widget/tool/chatactions/systemmessageaction.cpp \
|
||||||
widget/tool/chatactions/actionaction.cpp \
|
widget/tool/chatactions/actionaction.cpp \
|
||||||
|
historykeeper.cpp \
|
||||||
widget/maskablepixmapwidget.cpp
|
widget/maskablepixmapwidget.cpp
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "widget/maskablepixmapwidget.h"
|
#include "widget/maskablepixmapwidget.h"
|
||||||
#include "widget/croppinglabel.h"
|
#include "widget/croppinglabel.h"
|
||||||
#include "misc/style.h"
|
#include "misc/style.h"
|
||||||
|
#include "historykeeper.h"
|
||||||
|
|
||||||
ChatForm::ChatForm(Friend* chatFriend)
|
ChatForm::ChatForm(Friend* chatFriend)
|
||||||
: f(chatFriend)
|
: f(chatFriend)
|
||||||
|
@ -98,6 +99,8 @@ void ChatForm::onSendTriggered()
|
||||||
emit sendMessage(f->friendId, msg);
|
emit sendMessage(f->friendId, msg);
|
||||||
}
|
}
|
||||||
msgEdit->clear();
|
msgEdit->clear();
|
||||||
|
|
||||||
|
HistoryKeeper::getInstance()->addChatEntry(f->userId, msg, Core::getInstance()->getSelfId().publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onAttachClicked()
|
void ChatForm::onAttachClicked()
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
|
#include "historykeeper.h"
|
||||||
|
|
||||||
GroupChatForm::GroupChatForm(Group* chatGroup)
|
GroupChatForm::GroupChatForm(Group* chatGroup)
|
||||||
: group(chatGroup)
|
: group(chatGroup)
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include "historykeeper.h"
|
||||||
#include <tox/tox.h>
|
#include <tox/tox.h>
|
||||||
|
|
||||||
Widget *Widget::instance{nullptr};
|
Widget *Widget::instance{nullptr};
|
||||||
|
@ -498,6 +499,7 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool
|
||||||
return;
|
return;
|
||||||
|
|
||||||
f->chatForm->addMessage(f->getName(), message, isAction);
|
f->chatForm->addMessage(f->getName(), message, isAction);
|
||||||
|
HistoryKeeper::getInstance()->addChatEntry(f->userId, message, f->userId);
|
||||||
|
|
||||||
if (activeChatroomWidget != nullptr)
|
if (activeChatroomWidget != nullptr)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user