From da3151391b3c4835c4378f9ba8382d76cc727168 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 15 May 2019 20:36:37 +0300 Subject: [PATCH] First working prototype of history requesting, dates in messages, some screen scrolling handling --- core/account.cpp | 165 ++++++++++++++++++++++++++++++++------------ core/account.h | 5 ++ core/archive.cpp | 97 ++++++++++++++++++++------ core/archive.h | 4 +- core/contact.cpp | 119 ++++++++++++++++++-------------- core/contact.h | 4 +- core/squawk.cpp | 13 +++- core/squawk.h | 4 +- global.cpp | 23 ++++-- global.h | 3 + main.cpp | 5 +- ui/CMakeLists.txt | 7 ++ ui/conversation.cpp | 47 ++++++++++++- ui/conversation.h | 5 ++ ui/conversation.ui | 9 ++- ui/messageline.cpp | 20 +++++- ui/messageline.h | 5 ++ ui/squawk.cpp | 18 ++++- ui/squawk.h | 5 +- 19 files changed, 422 insertions(+), 136 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index d6d36e4..22b8e2e 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1,6 +1,7 @@ #include "account.h" #include #include +#include using namespace Core; @@ -25,6 +26,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected())); QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&))); QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&))); + QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error))); QXmppRosterManager& rm = client.rosterManager(); @@ -212,6 +214,8 @@ void Core::Account::addedAccount(const QString& jid) QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&))); QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)), this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState))); + QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&)), this, SLOT(onContactNeedHistory(const QString&, const QString&))); + QObject::connect(contact, SIGNAL(historyResponse(const std::list&)), this, SLOT(onContactHistoryResponse(const std::list&))); } } @@ -368,6 +372,10 @@ void Core::Account::sendMessage(const Shared::Message& data) QXmppMessage msg(data.getFrom(), data.getTo(), data.getBody(), data.getThread()); msg.setId(data.getId()); msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible + + std::map::const_iterator itr = contacts.find(data.getPenPalJid()); + itr->second->appendMessageToArchive(data); + client.sendPacket(msg); } else { qDebug() << "An attempt to send message with not connected account " << name << ", skipping"; @@ -386,27 +394,14 @@ void Core::Account::onCarbonMessageSent(const QXmppMessage& msg) bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) { - QString body(msg.body()); + const QString& body(msg.body()); if (body.size() != 0) { - QString id(msg.id()); - QDateTime time(msg.stamp()); + const QString& id(msg.id()); Shared::Message sMsg(Shared::Message::chat); - sMsg.setId(id); - sMsg.setFrom(msg.from()); - sMsg.setTo(msg.to()); - sMsg.setBody(body); - sMsg.setForwarded(forwarded); - if (guessing) { - if (sMsg.getFromJid() == getLogin() + "@" + getServer()) { - outgoing = true; - } else { - outgoing = false; - } - } - sMsg.setOutgoing(outgoing); - if (time.isValid()) { - sMsg.setTime(time); - } + initializeMessage(sMsg, msg, outgoing, forwarded, guessing); + std::map::const_iterator itr = contacts.find(sMsg.getPenPalJid()); + itr->second->appendMessageToArchive(sMsg); + emit message(sMsg); if (!forwarded && !outgoing) { @@ -422,13 +417,51 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo return false; } -void Core::Account::onMamMessageReceived(const QString& bareJid, const QXmppMessage& msg) +void Core::Account::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const { - handleChatMessage(msg, false, true, true); + const QDateTime& time(source.stamp()); + target.setId(source.id()); + target.setFrom(source.from()); + target.setTo(source.to()); + target.setBody(source.body()); + target.setForwarded(forwarded); + if (guessing) { + if (target.getFromJid() == getLogin() + "@" + getServer()) { + outgoing = true; + } else { + outgoing = false; + } + } + target.setOutgoing(outgoing); + if (time.isValid()) { + target.setTime(time); + } else { + target.setCurrentTime(); + } +} + +void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg) +{ + std::map::const_iterator itr = achiveQueries.find(queryId); + QString jid = itr->second; + std::map::const_iterator citr = contacts.find(jid); + + if (citr != contacts.end()) { + Contact* cnt = citr->second; + if (msg.id().size() > 0 && msg.body().size() > 0) { + Shared::Message sMsg(Shared::Message::chat); + initializeMessage(sMsg, msg, false, true, true); + + cnt->addMessageToArchive(sMsg); + } + + } + //handleChatMessage(msg, false, true, true); } void Core::Account::requestArchive(const QString& jid, int count, const QString& before) { + qDebug() << "An archive request for " << jid << ", before " << before; std::map::const_iterator itr = contacts.find(jid); if (itr == contacts.end()) { qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping"; @@ -436,40 +469,56 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString& } Contact* contact = itr->second; - contact->requestHistory(count, before); - Contact::ArchiveState as = contact->getArchiveState(); - switch (as) { - case Contact::empty: - break; - case Contact::beginning: - break; - case Contact::chunk: - break; - case Contact::complete: - break; - case Contact::end: - break; + if (contact->getArchiveState() == Contact::empty && before.size() == 0) { + QXmppMessage msg(getFullJid(), jid, "", ""); + QString last = Shared::generateUUID(); + msg.setId(last); + msg.setType(QXmppMessage::Chat); + msg.setState(QXmppMessage::Active); + client.sendPacket(msg); + QTimer* timer = new QTimer; + QObject::connect(timer, &QTimer::timeout, [timer, contact, count, last](){ + contact->requestFromEmpty(count, last); + timer->deleteLater(); + }); + + timer->setSingleShot(true); + timer->start(1000); + } else { + contact->requestHistory(count, before); } - +} + +void Core::Account::onContactNeedHistory(const QString& before, const QString& after) +{ + Contact* contact = static_cast(sender()); QXmppResultSetQuery query; query.setMax(100); - QDateTime from = QDateTime::currentDateTime().addDays(-7); - - QString q = am->retrieveArchivedMessages("", "", jid, from, QDateTime(), query); - achiveQueries.insert(std::make_pair(q, jid)); + if (before.size() > 0) { + query.setBefore(before); + } + if (after.size() > 0) { + query.setAfter(after); + } + + qDebug() << "Remote query from\"" << after << "\", to" << before; + + QString q = am->retrieveArchivedMessages("", "", contact->jid, QDateTime(), QDateTime(), query); + achiveQueries.insert(std::make_pair(q, contact->jid)); } + void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete) { std::map::const_iterator itr = achiveQueries.find(queryId); QString jid = itr->second; achiveQueries.erase(itr); - if (!complete) { - QXmppResultSetQuery q; - q.setAfter(resultSetReply.last()); - q.setMax(100); - QString nQ = am->retrieveArchivedMessages("", "", jid, QDateTime::currentDateTime().addDays(-7), QDateTime(), q); - achiveQueries.insert(std::make_pair(nQ, jid)); + std::map::const_iterator citr = contacts.find(jid); + if (citr != contacts.end()) { + Contact* cnt = citr->second; + + qDebug() << "Flushing messages for" << jid; + cnt->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last()); } } @@ -556,3 +605,29 @@ Shared::SubscriptionState Core::Account::castSubscriptionState(QXmppRosterIq::It } return state; } + +void Core::Account::onContactHistoryResponse(const std::list& list) +{ + Contact* contact = static_cast(sender()); + + qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements"; + emit responseArchive(contact->jid, list); +} + +void Core::Account::onClientError(QXmppClient::Error err) +{ + switch (err) { + case QXmppClient::SocketError: + qDebug() << "Client socket error" << client.socketErrorString(); + break; + case QXmppClient::XmppStreamError: + qDebug() << "Client stream error" << client.socketErrorString(); + break; + case QXmppClient::KeepAliveError: + qDebug() << "Client keep alive error"; + break; + } + + //onClientDisconnected(); +} + diff --git a/core/account.h b/core/account.h index 9b41cbf..04f14b6 100644 --- a/core/account.h +++ b/core/account.h @@ -55,6 +55,7 @@ signals: void addPresence(const QString& jid, const QString& name, const QMap& data); void removePresence(const QString& jid, const QString& name); void message(const Shared::Message& data); + void responseArchive(const QString& jid, const std::list& list); private: QString name; @@ -71,6 +72,7 @@ private: private slots: void onClientConnected(); void onClientDisconnected(); + void onClientError(QXmppClient::Error err); void onRosterReceived(); void onRosterItemAdded(const QString& bareJid); @@ -91,12 +93,15 @@ private slots: void onContactGroupRemoved(const QString& group); void onContactNameChanged(const QString& name); void onContactSubscriptionStateChanged(Shared::SubscriptionState state); + void onContactHistoryResponse(const std::list& list); + void onContactNeedHistory(const QString& before, const QString& after); private: void addedAccount(const QString &bareJid); bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); void addToGroup(const QString& jid, const QString& group); void removeFromGroup(const QString& jid, const QString& group); + void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const; Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const; }; diff --git a/core/archive.cpp b/core/archive.cpp index 5efe17e..21d1658 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -192,7 +192,7 @@ QString Core::Archive::newestId() qDebug() << "Error geting newestId " << mdb_strerror(rc); mdb_cursor_close(cursor); mdb_txn_abort(txn); - throw new Empty(jid.toStdString()); + throw Empty(jid.toStdString()); } else { std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size); mdb_cursor_close(cursor); @@ -220,6 +220,7 @@ unsigned int Core::Archive::addElements(const std::list& messag std::list::const_iterator itr = messages.begin(); while (rc == 0 && itr != messages.end()) { const Shared::Message& message = *itr; + QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); message.serialize(ds); @@ -230,6 +231,7 @@ unsigned int Core::Archive::addElements(const std::list& messag lmdbKey.mv_data = (char*)id.c_str(); lmdbData.mv_size = ba.size(); lmdbData.mv_data = (uint8_t*)ba.data(); + rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE); if (rc == 0) { MDB_val orderKey; @@ -249,6 +251,7 @@ unsigned int Core::Archive::addElements(const std::list& messag qDebug() << "An element couldn't been added to the archive, aborting the transaction" << mdb_strerror(rc); } } + itr++; } if (rc != 0) { @@ -279,7 +282,7 @@ QString Core::Archive::oldestId() qDebug() << "Error geting oldestId " << mdb_strerror(rc); mdb_cursor_close(cursor); mdb_txn_abort(txn); - throw new Empty(jid.toStdString()); + throw Empty(jid.toStdString()); } else { std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size); mdb_cursor_close(cursor); @@ -317,21 +320,21 @@ std::list Core::Archive::getBefore(int count, const QString& id rc = mdb_cursor_open(txn, order, &cursor); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST); if (rc) { - qDebug() << "Error geting before " << mdb_strerror(rc); + qDebug() << "Error getting before " << mdb_strerror(rc) << ", id:" << id; mdb_cursor_close(cursor); mdb_txn_abort(txn); - throw new Empty(jid.toStdString()); - } else { - std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size); + throw Empty(jid.toStdString()); } } else { - lmdbKey.mv_size = id.size(); - lmdbKey.mv_data = (char*)id.toStdString().c_str(); + std::string stdId(id.toStdString()); + lmdbKey.mv_size = stdId.size(); + lmdbKey.mv_data = (char*)stdId.c_str(); rc = mdb_get(txn, main, &lmdbKey, &lmdbData); if (rc) { - qDebug() <<"Error getting before: " << mdb_strerror(rc); + qDebug() <<"Error getting before: no reference message" << mdb_strerror(rc) << ", id:" << id; mdb_txn_abort(txn); - throw NotFound(id.toStdString(), jid.toStdString()); + printKeys(); + throw NotFound(stdId, jid.toStdString()); } else { QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); QDataStream ds(&ba, QIODevice::ReadOnly); @@ -346,29 +349,40 @@ std::list Core::Archive::getBefore(int count, const QString& id rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET); if (rc) { - qDebug() << "Error getting before " << mdb_strerror(rc); + qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc); mdb_cursor_close(cursor); mdb_txn_abort(txn); - throw new NotFound(id.toStdString(), jid.toStdString()); + throw NotFound(stdId, jid.toStdString()); } else { rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV); if (rc) { - qDebug() << "Error getting before " << mdb_strerror(rc); + qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc); mdb_cursor_close(cursor); mdb_txn_abort(txn); - throw new NotFound(id.toStdString(), jid.toStdString()); + throw NotFound(stdId, jid.toStdString()); } } } } do { - QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); - QDataStream ds(&ba, QIODevice::ReadOnly); - - res.emplace_back(); - Shared::Message& msg = res.back(); - msg.deserialize(ds); + MDB_val dKey, dData; + dKey.mv_size = lmdbData.mv_size; + dKey.mv_data = lmdbData.mv_data; + rc = mdb_get(txn, main, &dKey, &dData); + if (rc) { + qDebug() <<"Get error: " << mdb_strerror(rc); + std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size); + mdb_txn_abort(txn); + throw NotFound(sId, jid.toStdString()); + } else { + QByteArray ba((char*)dData.mv_data, dData.mv_size); + QDataStream ds(&ba, QIODevice::ReadOnly); + + res.emplace_back(); + Shared::Message& msg = res.back(); + msg.deserialize(ds); + } --count; @@ -420,7 +434,7 @@ bool Core::Archive::isFromTheBeginning() return fromTheBeginning; } -bool Core::Archive::setFromTheBeginning(bool is) +void Core::Archive::setFromTheBeginning(bool is) { if (!opened) { throw Closed("setFromTheBeginning", jid.toStdString()); @@ -448,3 +462,44 @@ bool Core::Archive::setFromTheBeginning(bool is) } } } + +void Core::Archive::printOrder() +{ + qDebug() << "Printing order"; + MDB_txn *txn; + int rc; + rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + MDB_cursor* cursor; + rc = mdb_cursor_open(txn, order, &cursor); + MDB_val lmdbKey, lmdbData; + + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + + do { + std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size); + qDebug() << QString(sId.c_str()); + } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0); + + mdb_cursor_close(cursor); + mdb_txn_abort(txn); +} + +void Core::Archive::printKeys() +{ + MDB_txn *txn; + int rc; + rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + MDB_cursor* cursor; + rc = mdb_cursor_open(txn, main, &cursor); + MDB_val lmdbKey, lmdbData; + + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + + do { + std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size); + qDebug() << QString(sId.c_str()); + } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0); + + mdb_cursor_close(cursor); + mdb_txn_abort(txn); +} diff --git a/core/archive.h b/core/archive.h index b887359..a55c2ee 100644 --- a/core/archive.h +++ b/core/archive.h @@ -48,7 +48,7 @@ public: long unsigned int size() const; std::list getBefore(int count, const QString& id); bool isFromTheBeginning(); - bool setFromTheBeginning(bool is); + void setFromTheBeginning(bool is); public: const QString jid; @@ -109,6 +109,8 @@ private: MDB_dbi stats; bool _isFromTheBeginning(); + void printOrder(); + void printKeys(); }; } diff --git a/core/contact.cpp b/core/contact.cpp index 2b29003..1a1203a 100644 --- a/core/contact.cpp +++ b/core/contact.cpp @@ -17,6 +17,7 @@ */ #include "contact.h" +#include Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent): QObject(parent), @@ -152,7 +153,7 @@ void Core::Contact::performRequest(int count, const QString& before) switch (archiveState) { case empty: - emit needHistory(before, "", QDateTime(), QDateTime()); + emit needHistory(before, ""); break; case chunk: case beginning: @@ -160,33 +161,48 @@ void Core::Contact::performRequest(int count, const QString& before) requestCache.emplace_back(requestedCount, before); requestedCount = -1; } - emit needHistory("", archive->newestId(), QDateTime(), QDateTime()); + emit needHistory("", archive->newestId()); break; case end: if (count != -1) { - bool found = requestFromArchive(before); + QString lBefore; + if (responseCache.size() > 0) { + lBefore = responseCache.front().getId(); + } else { + lBefore = before; + } + bool found = false; + try { + std::list arc = archive->getBefore(requestedCount - responseCache.size(), lBefore); + responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); + found = true; + } catch (Archive::NotFound e) { + requestCache.emplace_back(requestedCount, before); + requestedCount = -1; + emit needHistory(archive->oldestId(), ""); + } + if (found) { int rSize = responseCache.size(); if (rSize < count) { if (rSize != 0) { - emit needHistory(responseCache.front().getId(), "", QDateTime(), QDateTime()); + emit needHistory(responseCache.front().getId(), ""); } else { - emit needHistory(before, "", QDateTime(), QDateTime()); + emit needHistory(before, ""); } } else { nextRequest(); } - } else { - requestCache.emplace_back(requestedCount, before); - requestedCount = -1; - emit needHistory(archive->oldestId(), "", QDateTime(), QDateTime()); } } else { - emit needHistory(archive->oldestId(), "", QDateTime(), QDateTime()); + emit needHistory(archive->oldestId(), ""); } break; case complete: - if (!requestFromArchive(before)) { + try { + std::list arc = archive->getBefore(requestedCount - responseCache.size(), before); + responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); + } catch (Archive::NotFound e) { qDebug("requesting id hasn't been found in archive, skipping"); } nextRequest(); @@ -194,38 +210,6 @@ void Core::Contact::performRequest(int count, const QString& before) } } -bool Core::Contact::requestFromArchive(const QString& before) { - std::list arc; - QString lBefore; - if (responseCache.size() > 0) { - lBefore = responseCache.front().getId(); - } else { - lBefore = before; - } - if (requestedCount != -1) { - try { - arc = archive->getBefore(requestedCount - responseCache.size(), lBefore); - responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); - return true; - } catch (Archive::NotFound e) { - requestCache.emplace_back(requestedCount, before); - requestedCount = -1; - emit needHistory(archive->oldestId(), "", QDateTime(), QDateTime()); - return false; - } - } else { - try { - arc = archive->getBefore(1, lBefore); - //just do nothing since response is not required - //may be even it's a signal that the history is now complete? - return true; - } catch (Archive::NotFound e) { - emit needHistory(archive->oldestId(), "", QDateTime(), QDateTime()); - return false; - } - } -} - void Core::Contact::appendMessageToArchive(const Shared::Message& msg) { const QString& id = msg.getId(); @@ -267,8 +251,10 @@ void Core::Contact::appendMessageToArchive(const Shared::Message& msg) void Core::Contact::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId) { + unsigned int added(0); if (hisoryCache.size() > 0) { - archive->addElements(hisoryCache); + added = archive->addElements(hisoryCache); + qDebug() << "Added" << added << "messages to the archive"; hisoryCache.clear(); } @@ -279,14 +265,14 @@ void Core::Contact::flushMessagesToArchive(bool finished, const QString& firstId archiveState = complete; nextRequest(); } else { - emit needHistory("", lastId, QDateTime(), QDateTime()); + emit needHistory("", lastId); } case chunk: if (finished) { archiveState = end; nextRequest(); } else { - emit needHistory("", lastId, QDateTime(), QDateTime()); + emit needHistory("", lastId); } break; case empty: @@ -303,20 +289,28 @@ void Core::Contact::flushMessagesToArchive(bool finished, const QString& firstId } else { before = requestedBefore; } - if (!requestFromArchive(before)) { - qDebug("Something went terrible wrong flushing messages to the archive"); - } - if (requestedCount < responseCache.size()) { + + bool found = false; + try { + std::list arc = archive->getBefore(requestedCount - responseCache.size(), before); + responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); + found = true; + } catch (Archive::NotFound e) {} + if (!found || requestedCount < responseCache.size()) { if (archiveState == complete) { nextRequest(); } else { - emit needHistory(firstId, "", QDateTime(), QDateTime()); + emit needHistory(firstId, ""); } } else { nextRequest(); } } else { - nextRequest(); + if (added != 0) { + nextRequest(); + } else { + emit needHistory(firstId, ""); + } } break; case complete: @@ -324,3 +318,24 @@ void Core::Contact::flushMessagesToArchive(bool finished, const QString& firstId break; } } + +void Core::Contact::requestFromEmpty(int count, const QString& before) +{ + if (syncronizing) { + qDebug("perform from empty didn't work, another request queued"); + } else { + if (archiveState != empty) { + qDebug("perform from empty didn't work, the state is not empty"); + requestHistory(count, before); + } else { + syncronizing = true; + requestedCount = count; + requestedBefore = ""; + hisoryCache.clear(); + responseCache.clear(); + + emit needHistory(before, ""); + } + } +} + diff --git a/core/contact.h b/core/contact.h index 80c2db0..650b06d 100644 --- a/core/contact.h +++ b/core/contact.h @@ -54,6 +54,7 @@ public: void appendMessageToArchive(const Shared::Message& msg); void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId); void requestHistory(int count, const QString& before); + void requestFromEmpty(int count, const QString& before); signals: void groupAdded(const QString& name); @@ -61,7 +62,7 @@ signals: void nameChanged(const QString& name); void subscriptionStateChanged(Shared::SubscriptionState state); void historyResponse(const std::list& messages); - void needHistory(const QString& before, const QString& after, const QDateTime& from, const QDateTime& to); + void needHistory(const QString& before, const QString& after); public: const QString jid; @@ -84,7 +85,6 @@ private: private: void nextRequest(); void performRequest(int count, const QString& before); - bool requestFromArchive(const QString& before); }; } diff --git a/core/squawk.cpp b/core/squawk.cpp index 25d8eb5..2d20941 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -96,6 +96,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const this, SLOT(onAccountAddPresence(const QString&, const QString&, const QMap&))); connect(acc, SIGNAL(removePresence(const QString&, const QString&)), this, SLOT(onAccountRemovePresence(const QString&, const QString&))); connect(acc, SIGNAL(message(const Shared::Message&)), this, SLOT(onAccountMessage(const Shared::Message&))); + connect(acc, SIGNAL(responseArchive(const QString&, const std::list&)), + this, SLOT(onAccountResponseArchive(const QString&, const std::list&))); QMap map = { {"login", login}, @@ -221,12 +223,19 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da itr->second->sendMessage(data); } -void Core::Squawk::requestArchive(const QString& account, const QString& jid) +void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { qDebug("An attempt to request an archive of non existing account, skipping"); return; } - itr->second->requestAchive(jid); + itr->second->requestArchive(jid, count, before); } + +void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list& list) +{ + Account* acc = static_cast(sender()); + emit responseArchive(acc->getName(), jid, list); +} + diff --git a/core/squawk.h b/core/squawk.h index 9b8235e..3daf26f 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -36,6 +36,7 @@ signals: void removePresence(const QString& account, const QString& jid, const QString& name); void stateChanged(int state); void accountMessage(const QString& account, const Shared::Message& data); + void responseArchive(const QString& account, const QString& jid, const std::list& list); public slots: void start(); @@ -45,7 +46,7 @@ public slots: void disconnectAccount(const QString& account); void changeState(int state); void sendMessage(const QString& account, const Shared::Message& data); - void requestArchive(const QString& account, const QString& jid); + void requestArchive(const QString& account, const QString& jid, int count, const QString& before); private: typedef std::deque Accounts; @@ -70,6 +71,7 @@ private slots: void onAccountAddPresence(const QString& jid, const QString& name, const QMap& data); void onAccountRemovePresence(const QString& jid, const QString& name); void onAccountMessage(const Shared::Message& data); + void onAccountResponseArchive(const QString& jid, const std::list& list); }; } diff --git a/global.cpp b/global.cpp index ce5556a..5d03fef 100644 --- a/global.cpp +++ b/global.cpp @@ -176,12 +176,7 @@ bool Shared::Message::getForwarded() const void Shared::Message::generateRandomId() { - uuid_t uuid; - uuid_generate(uuid); - - char uuid_str[36]; - uuid_unparse_lower(uuid, uuid_str); - id = uuid_str; + id = generateUUID(); } QString Shared::Message::getThread() const @@ -241,3 +236,19 @@ void Shared::Message::deserialize(QDataStream& data) data >> outgoing; data >> forwarded; } + +QString Shared::generateUUID() +{ + uuid_t uuid; + uuid_generate(uuid); + + char uuid_str[36]; + uuid_unparse_lower(uuid, uuid_str); + return uuid_str; +} + +void Shared::Message::setCurrentTime() +{ + time = QDateTime::currentDateTime(); +} + diff --git a/global.h b/global.h index 922c242..e399907 100644 --- a/global.h +++ b/global.h @@ -53,6 +53,8 @@ static const std::deque availabilityNames = {"Online", "Away", "Absent" static const std::deque subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"}; +QString generateUUID(); + class Message { public: enum Type { @@ -78,6 +80,7 @@ public: void setOutgoing(bool og); void setForwarded(bool fwd); void setType(Type t); + void setCurrentTime(); QString getFrom() const; QString getFromJid() const; diff --git a/main.cpp b/main.cpp index 7d0dae2..87ee2b5 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,7 @@ int main(int argc, char *argv[]) { qRegisterMetaType("Shared::Message"); + qRegisterMetaType>("std::list"); QApplication app(argc, argv); SignalCatcher sc(&app); @@ -35,7 +36,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, SIGNAL(disconnectAccount(const QString&)), squawk, SLOT(disconnectAccount(const QString&))); QObject::connect(&w, SIGNAL(changeState(int)), squawk, SLOT(changeState(int))); QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&)), squawk, SLOT(sendMessage(const QString&, const Shared::Message&))); - QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&)), squawk, SLOT(requestArchive(const QString&, const QString&))); + QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&, int, const QString&)), squawk, SLOT(requestArchive(const QString&, const QString&, int, const QString&))); QObject::connect(squawk, SIGNAL(newAccount(const QMap&)), &w, SLOT(newAccount(const QMap&))); QObject::connect(squawk, SIGNAL(accountAvailabilityChanged(const QString&, int)), &w, SLOT(accountAvailabilityChanged(const QString&, int))); @@ -53,6 +54,8 @@ int main(int argc, char *argv[]) QObject::connect(squawk, SIGNAL(removePresence(const QString&, const QString&, const QString&)), &w, SLOT(removePresence(const QString&, const QString&, const QString&))); QObject::connect(squawk, SIGNAL(stateChanged(int)), &w, SLOT(stateChanged(int))); QObject::connect(squawk, SIGNAL(accountMessage(const QString&, const Shared::Message&)), &w, SLOT(accountMessage(const QString&, const Shared::Message&))); + QObject::connect(squawk, SIGNAL(responseArchive(const QString&, const QString&, const std::list&)), + &w, SLOT(responseArchive(const QString&, const QString&, const std::list&))); //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index c5f7a42..e2fbf4c 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -8,6 +8,10 @@ set(CMAKE_AUTOUIC ON) # Find the QtWidgets library find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5Qml CONFIG REQUIRED) +find_package(Qt5QuickCompiler) +find_package(Qt5Quick CONFIG REQUIRED) +find_package(Qt5QuickWidgets CONFIG REQUIRED) set(squawkUI_SRC squawk.cpp @@ -28,6 +32,9 @@ add_library(squawkUI ${squawkUI_SRC}) # Use the Widgets module from Qt 5. target_link_libraries(squawkUI Qt5::Widgets) +target_link_libraries(squawkUI Qt5::Quick) +target_link_libraries(squawkUI Qt5::Qml) +target_link_libraries(squawkUI Qt5::QuickWidgets) # Install the executable install(TARGETS squawkUI DESTINATION lib) diff --git a/ui/conversation.cpp b/ui/conversation.cpp index b7ba1d8..6c2cb4f 100644 --- a/ui/conversation.cpp +++ b/ui/conversation.cpp @@ -20,6 +20,7 @@ #include "ui_conversation.h" #include #include +#include Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): QWidget(parent), @@ -30,7 +31,9 @@ Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): activePalResource(), thread(), scroll(down), - manualSliderChange(false) + manualSliderChange(false), + requestingHistory(false), + everShown(false) { m_ui->setupUi(this); m_ui->splitter->setSizes({300, 0}); @@ -116,6 +119,7 @@ void Conversation::addMessage(const Shared::Message& data) { int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition(); int max = m_ui->scrollArea->verticalScrollBar()->maximum(); + MessageLine::Position place = line->message(data); if (place == MessageLine::invalid) { return; @@ -183,6 +187,7 @@ void Conversation::onEnterPressed() msg.setBody(body); msg.setOutgoing(true); msg.generateRandomId(); + msg.setCurrentTime(); addMessage(msg); emit sendMessage(msg); } @@ -195,6 +200,18 @@ void Conversation::onMessagesResize(int amount) case down: m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum()); break; + case keep: { + int max = m_ui->scrollArea->verticalScrollBar()->maximum(); + int value = m_ui->scrollArea->verticalScrollBar()->value() + amount; + m_ui->scrollArea->verticalScrollBar()->setValue(value); + + if (value == max) { + scroll = down; + } else { + scroll = nothing; + } + } + break; default: break; } @@ -207,7 +224,35 @@ void Conversation::onSliderValueChanged(int value) if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) { scroll = down; } else { + if (!requestingHistory && value == 0) { + m_ui->historyStatus->setPixmap(QIcon::fromTheme("view-refresh").pixmap(25)); + requestingHistory = true; + emit requestArchive(line->firstMessageId()); + } scroll = nothing; } } } + +void Conversation::responseArchive(const std::list list) +{ + requestingHistory = false; + scroll = keep; + + m_ui->historyStatus->clear(); + for (std::list::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) { + addMessage(*itr); + } +} + +void Conversation::showEvent(QShowEvent* event) +{ + if (!everShown) { + everShown = true; + m_ui->historyStatus->setPixmap(QIcon::fromTheme("view-refresh").pixmap(25)); + requestingHistory = true; + emit requestArchive(line->firstMessageId()); + } + + QWidget::showEvent(event); +} diff --git a/ui/conversation.h b/ui/conversation.h index e88007b..3f5a315 100644 --- a/ui/conversation.h +++ b/ui/conversation.h @@ -56,9 +56,12 @@ public: void addMessage(const Shared::Message& data); void setPalResource(const QString& res); + void responseArchive(const std::list list); + void showEvent(QShowEvent * event) override; signals: void sendMessage(const Shared::Message& message); + void requestArchive(const QString& before); protected: void setState(Shared::Availability state); @@ -85,6 +88,8 @@ private: QString thread; Scroll scroll; bool manualSliderChange; + bool requestingHistory; + bool everShown; }; #endif // CONVERSATION_H diff --git a/ui/conversation.ui b/ui/conversation.ui index e1b2a52..de6604e 100644 --- a/ui/conversation.ui +++ b/ui/conversation.ui @@ -105,6 +105,13 @@ + + + + + + + @@ -134,7 +141,7 @@ 0 0 572 - 162 + 95 diff --git a/ui/messageline.cpp b/ui/messageline.cpp index 4fd7205..7cb53bb 100644 --- a/ui/messageline.cpp +++ b/ui/messageline.cpp @@ -76,6 +76,12 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) QLabel* body = new QLabel(msg.getBody()); QLabel* sender = new QLabel(); + QLabel* time = new QLabel(msg.getTime().toLocalTime().toString()); + QFont dFont = time->font(); + dFont.setItalic(true); + dFont.setPointSize(dFont.pointSize() - 2); + time->setFont(dFont); + time->setForegroundRole(QPalette::ToolTipText); QFont f; f.setBold(true); sender->setFont(f); @@ -84,10 +90,12 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) vBox->addWidget(sender); vBox->addWidget(body); + vBox->addWidget(time); if (msg.getOutgoing()) { - body->setAlignment(Qt::AlignRight); + //body->setAlignment(Qt::AlignRight); sender->setAlignment(Qt::AlignRight); + time->setAlignment(Qt::AlignRight); sender->setText(myName); hBox->addStretch(); hBox->addWidget(message); @@ -132,3 +140,13 @@ void MessageLine::resizeEvent(QResizeEvent* event) QWidget::resizeEvent(event); emit resize(event->size().height() - event->oldSize().height()); } + + +QString MessageLine::firstMessageId() const +{ + if (messageOrder.size() == 0) { + return ""; + } else { + return messageOrder.begin()->second->getId(); + } +} diff --git a/ui/messageline.h b/ui/messageline.h index c0fd0d5..9dbc192 100644 --- a/ui/messageline.h +++ b/ui/messageline.h @@ -25,6 +25,7 @@ #include #include #include "../global.h" +#include class MessageLine : public QWidget { @@ -42,6 +43,9 @@ public: Position message(const Shared::Message& msg); void setMyName(const QString& name); void setPalName(const QString& jid, const QString& name); + QString firstMessageId() const; + void showBusyIndicator(); + void hideBusyIndicator(); signals: void resize(int amount); @@ -67,6 +71,7 @@ private: QString myName; std::map palNames; std::deque views; + QQuickWidget busy; }; #endif // MESSAGELINE_H diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 0d91ca3..6dcd78d 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -189,6 +189,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) conv->setAttribute(Qt::WA_DeleteOnClose); connect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*))); connect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&))); + connect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&))); conversations.insert(std::make_pair(id, conv)); rosterModel.dropMessages(account, jid); @@ -198,7 +199,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) if (res.size() > 0) { itr->second->setPalResource(res); } - requestArchive(account, jid); } } } @@ -234,3 +234,19 @@ void Squawk::onConversationMessage(const Shared::Message& msg) emit sendMessage(conv->getAccount(), msg); } + +void Squawk::onConversationRequestArchive(const QString& before) +{ + Conversation* conv = static_cast(sender()); + requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value +} + +void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list) +{ + Models::Roster::ElId id(account, jid); + + Conversations::const_iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->responseArchive(list); + } +} diff --git a/ui/squawk.h b/ui/squawk.h index b9d53bd..573e1ac 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "accounts.h" #include "conversation.h" @@ -31,7 +32,7 @@ signals: void disconnectAccount(const QString&); void changeState(int state); void sendMessage(const QString& account, const Shared::Message& data); - void requestArchive(const QString& account, const QString& jid); + void requestArchive(const QString& account, const QString& jid, int count, const QString& before); public slots: void newAccount(const QMap& account); @@ -47,6 +48,7 @@ public slots: void removePresence(const QString& account, const QString& jid, const QString& name); void stateChanged(int state); void accountMessage(const QString& account, const Shared::Message& data); + void responseArchive(const QString& account, const QString& jid, const std::list& list); private: typedef std::map Conversations; @@ -66,6 +68,7 @@ private slots: void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); void onConversationMessage(const Shared::Message& msg); + void onConversationRequestArchive(const QString& before); };