#include "jiddb.h" #include #include #include #include #include #include #include JidDb::JidDb(const QString &jid) : jid(jid), db(QSqlDatabase::addDatabase("QSQLITE")) { const auto path = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation); const QDir dir; if (!dir.exists(path) && !dir.mkdir(path)) throw std::runtime_error("Failed to create app dir"); const auto abspath = path + "/" + jid + ".db"; db.setDatabaseName(abspath); if (!db.open()) throw std::runtime_error(qPrintable("database" + abspath + "could not be created")); QSqlQuery q(db); if (!q.exec("create table if not exists roster " "(jid TEXT unique) strict;")) std::cerr << "JidDb::JidDb: query exec failed" << std::endl; } int JidDb::addToRoster(const QString &jid) { QSqlQuery q(db); if (!q.exec("insert or ignore into roster (jid) values ('" + jid + "');")) { std::cerr << "JidDb::addToRoster: query exec failed" << std::endl; return -1; } Q_EMIT addedToRoster(jid); return 0; } int JidDb::addToRoster(const QStringList &roster) { for (const auto &r : roster) if (addToRoster(r)) return -1; return 0; } QStringList JidDb::roster() const { QStringList ret; QSqlQuery q(db); if (!q.exec("select jid from roster;")) std::cerr << "query exec failed" << std::endl; else while (q.next()) ret.append(q.value("jid").toString()); return ret; } int JidDb::ensureContactDb(const QString &jid) const { QSqlQuery q(db); if (!q.exec("create table if not exists '" + jid + "' (direction TEXT, time INTEGER, body TEXT) strict;")) { std::cerr << "JidDb::storeMessage: query exec failed" << std::endl; return -1; } return 0; } QList JidDb::getMessages(const QString &jid, const int tail) const { QSqlQuery q(db); if (ensureContactDb(jid)) return QList(); else if (tail < 0) { if (!q.exec("select * from '" + jid + "' order by time;")) { std::cerr << "JidDb::getMessages: query exec failed"; return QList(); } } else if (!q.exec("select * from '" + jid + "' order by time desc limit " + QString::number(tail) + ";")) { std::cerr << "JidDb::getMessages: query exec failed" << std::endl; return QList(); } QList ret; while (q.next()) { Message m; bool ok; qlonglong t = q.value("time").toLongLong(&ok); if (!ok) { std::cerr << "JidDb::getMessages: invalid time" << std::endl; // Attempt to read other messages. continue; } m.body = q.value("body").toString(); m.dt = QDateTime::fromSecsSinceEpoch(t, QTimeZone::systemTimeZone()); const auto dirstr = q.value("direction").toString(); if (dirstr == "in") m.direction = Direction::In; else if (dirstr == "out") m.direction = Direction::Out; else { std::cerr << "JidDb::getMessages: invalid direction" << std::endl; // Attempt to read other messages. continue; } ret << m; } return ret; } int JidDb::storeMessage(const JidDb::Message &msg) const { QSqlQuery q(db); QString dir; switch (msg.direction) { case Direction::In: dir = "in"; break; case Direction::Out: dir = "out"; break; } ensureContactDb(msg.contact); if (!q.exec("insert into '" + msg.contact + "' (direction, time, body) values " "('" + dir + "', " + QString::number(msg.dt.toSecsSinceEpoch()) + ", " "'" + msg.body + "')")) { std::cerr << "JidDb::storeMessage: query exec 2 failed" << std::endl; return -1; } return 0; } QStringList JidDb::tables() const { QSqlQuery q(db); if (!q.exec("select name from sqlite_schema where " "type = 'table' and name not like 'sqlite_%'")) { std::cerr << "JidDb::getConversations: query failed" << std::endl; return QStringList(); } QStringList ret; while (q.next()) ret << q.value("name").toString(); return ret; } QList JidDb::getConversations() const { const auto conversations = tables(); QList ret; for (const auto &jid : conversations) if (jid.contains('@')) { const auto messages = getMessages(jid, 1); if (!messages.isEmpty()) { const auto &m = messages.first(); ret << Conversation(jid, m.body, m.dt); } } return ret; }