File indexing completed on 2023-12-03 08:28:34

0001 /*
0002     Copyright (C) 2011  Dominik Schmidt <kde@dominik-schmidt.de>
0003     Copyright (C) 2013  Daniel Vrátil <dvratil@redhat.com>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Lesser General Public
0007     License as published by the Free Software Foundation; either
0008     version 2.1 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Lesser General Public License for more details.
0014 
0015     You should have received a copy of the GNU Lesser General Public
0016     License along with this library; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 
0021 #include "scrollback-manager.h"
0022 
0023 #include "../message-processor.h"
0024 #include "log-entity.h"
0025 #include "log-manager.h"
0026 #include "pending-logger-dates.h"
0027 #include "pending-logger-logs.h"
0028 
0029 #include "debug.h"
0030 
0031 #include <TelepathyQt/Types>
0032 #include <TelepathyQt/AvatarData>
0033 #include <TelepathyQt/TextChannel>
0034 #include <TelepathyQt/ReceivedMessage>
0035 
0036 class ScrollbackManager::Private
0037 {
0038   public:
0039     Private(): scrollbackLength(10)
0040     {
0041     }
0042 
0043     Tp::AccountPtr account;
0044     Tp::TextChannelPtr textChannel;
0045     KTp::LogEntity contactEntity;
0046     int scrollbackLength;
0047     QList<QDate> datesCache;
0048     QList<KTp::LogMessage> messagesCache;
0049     QString fromMessageToken;
0050 };
0051 
0052 ScrollbackManager::ScrollbackManager(QObject *parent)
0053     : QObject(parent),
0054     d(new Private)
0055 {
0056 }
0057 
0058 ScrollbackManager::~ScrollbackManager()
0059 {
0060     delete d;
0061 }
0062 
0063 bool ScrollbackManager::exists() const
0064 {
0065     if (d->account.isNull() || d->textChannel.isNull() ) {
0066         return false;
0067     }
0068 
0069     return KTp::LogManager::instance()->logsExist(d->account, d->contactEntity);
0070 }
0071 
0072 void ScrollbackManager::setTextChannel(const Tp::AccountPtr &account, const Tp::TextChannelPtr &textChannel)
0073 {
0074     d->textChannel = textChannel;
0075     d->account = account;
0076 
0077     if (d->account.isNull() || d->textChannel.isNull()) {
0078         return;
0079     }
0080 
0081     KTp::LogEntity contactEntity;
0082     if (d->textChannel->targetHandleType() == Tp::HandleTypeContact) {
0083         d->contactEntity = KTp::LogEntity(d->textChannel->targetHandleType(),
0084                                        d->textChannel->targetContact()->id(),
0085                                        d->textChannel->targetContact()->alias());
0086     } else if (d->textChannel->targetHandleType() == Tp::HandleTypeRoom) {
0087         d->contactEntity = KTp::LogEntity(d->textChannel->targetHandleType(),
0088                                        d->textChannel->targetId());
0089     }
0090 }
0091 
0092 void ScrollbackManager::setAccountAndContact(const Tp::AccountPtr &account, const QString &contactId, const QString &contactAlias)
0093 {
0094     d->account = account;
0095 
0096     if (d->account.isNull()) {
0097         return;
0098     }
0099 
0100     d->contactEntity = KTp::LogEntity(Tp::HandleTypeContact,
0101                                       contactId,
0102                                       contactAlias);
0103 }
0104 
0105 void ScrollbackManager::setScrollbackLength(int n)
0106 {
0107     d->scrollbackLength = n;
0108 }
0109 
0110 int ScrollbackManager::scrollbackLength() const
0111 {
0112     return d->scrollbackLength;
0113 }
0114 
0115 void ScrollbackManager::fetchScrollback()
0116 {
0117     fetchHistory(d->scrollbackLength);
0118 }
0119 
0120 void ScrollbackManager::fetchHistory(int n, const QString &fromMessageToken)
0121 {
0122     if (n > 0 && !d->account.isNull() && d->contactEntity.isValid()) {
0123         d->fromMessageToken = fromMessageToken;
0124         KTp::LogManager *manager = KTp::LogManager::instance();
0125         KTp::PendingLoggerDates *dates = manager->queryDates(d->account, d->contactEntity);
0126         connect(dates, SIGNAL(finished(KTp::PendingLoggerOperation*)),
0127                 this, SLOT(onDatesFinished(KTp::PendingLoggerOperation*)));
0128         return;
0129     }
0130 
0131     //in all other cases finish immediately.
0132     QList<KTp::Message> messages;
0133     Q_EMIT fetched(messages);
0134 }
0135 
0136 void ScrollbackManager::onDatesFinished(KTp::PendingLoggerOperation* po)
0137 {
0138     KTp::PendingLoggerDates *datesOp = qobject_cast<KTp::PendingLoggerDates*>(po);
0139     if (datesOp->hasError()) {
0140         qCWarning(KTP_LOGGER) << "Failed to fetch dates:" << datesOp->error();
0141         Q_EMIT fetched(QList<KTp::Message>());
0142         return;
0143     }
0144 
0145     const QList<QDate> dates = datesOp->dates();
0146     if (dates.isEmpty()) {
0147         Q_EMIT fetched(QList<KTp::Message>());
0148         return;
0149     }
0150 
0151 
0152     // Store all the fetched dates for later reusing
0153     d->datesCache = dates;
0154 
0155     // Request all logs for the last date in the datesCache and remove
0156     // it from the cache
0157     KTp::LogManager *manager = KTp::LogManager::instance();
0158     KTp::PendingLoggerLogs *logs = manager->queryLogs(datesOp->account(), datesOp->entity(),
0159                                                       d->datesCache.takeLast());
0160     connect(logs, SIGNAL(finished(KTp::PendingLoggerOperation*)),
0161             this, SLOT(onEventsFinished(KTp::PendingLoggerOperation*)));
0162 }
0163 
0164 void ScrollbackManager::onEventsFinished(KTp::PendingLoggerOperation *op)
0165 {
0166     KTp::PendingLoggerLogs *logsOp = qobject_cast<KTp::PendingLoggerLogs*>(op);
0167     if (logsOp->hasError()) {
0168         qCWarning(KTP_LOGGER) << "Failed to fetch events:" << logsOp->error();
0169         Q_EMIT fetched(QList<KTp::Message>());
0170         return;
0171     }
0172 
0173     QStringList queuedMessageTokens;
0174     if (!d->textChannel.isNull()) {
0175         Q_FOREACH(const Tp::ReceivedMessage &message, d->textChannel->messageQueue()) {
0176             queuedMessageTokens.append(message.messageToken());
0177         }
0178     }
0179 
0180     // get last n (d->fetchLast) messages that are not queued
0181     QList<KTp::LogMessage> allMessages = logsOp->logs();
0182 
0183     // First of all check if the "fromMessageToken" was specified and if yes,
0184     // then look if any message in the current fetched logs set
0185     // contains the requested message that the logs should start from
0186     // In case the token is empty, it compares message date and message body
0187     if (!d->fromMessageToken.isEmpty()) {
0188         int i = 0;
0189         for (i = 0; i < allMessages.size(); i++) {
0190             if (allMessages.at(i).token().isEmpty()) {
0191                 const QString token = allMessages.at(i).time().toString(Qt::ISODate) + allMessages.at(i).mainMessagePart();
0192                 if (token == d->fromMessageToken) {
0193                     break;
0194                 }
0195             } else {
0196                 if (allMessages.at(i).token() == d->fromMessageToken) {
0197                     break;
0198                 }
0199             }
0200         }
0201 
0202         // If the message with the "fromMessageToken" is in the current logs set,
0203         // drop all the messages beyond the one with "fromMessageToken" as we don't
0204         // care about those. If the message is not in this set, clear all the fetched
0205         // messages set (the querying works backwards from the most recent dates; so
0206         // if this set does not contain the message with the specified token, it will
0207         // be in some older set and all these newer messages than the "fromMessageToken"
0208         // message are useless)
0209         if (i < allMessages.size()) {
0210             allMessages = allMessages.mid(0, i);
0211 
0212             // Clear the fromMessageToken to not break fetching more logs from history.
0213             // For example: the current set has the requested-token message but has
0214             // only 3 messages, so below it queries further in the history to fetch
0215             // more messages. If the token would not be empty when the new request finishes,
0216             // it would discard the fetched messages in the following else branch
0217             // resulting in no messages being fetched
0218             d->fromMessageToken.clear();
0219         } else {
0220             allMessages.clear();
0221         }
0222     }
0223 
0224     // The messages are fetched backwards - the most recent
0225     // date is fetched first, so take the messages from the previous
0226     // dates and append them to the messages from the current date,
0227     // that will sort them with newest dates first.
0228     // This is useful only for the case below when the fetched messages
0229     // are less than the requested scrollback length
0230     allMessages.append(d->messagesCache);
0231 
0232     // If the logs for the last date were too few, cache the
0233     // retrieved messages and request logs from another date
0234     if (allMessages.size() < d->scrollbackLength) {
0235         // Only request more logs when there are more dates to query
0236         if (!d->datesCache.isEmpty()) {
0237             d->messagesCache = allMessages;
0238 
0239             KTp::LogManager *manager = KTp::LogManager::instance();
0240             KTp::PendingLoggerLogs *logs = manager->queryLogs(logsOp->account(), logsOp->entity(),
0241                                                             d->datesCache.takeLast());
0242             connect(logs, SIGNAL(finished(KTp::PendingLoggerOperation*)),
0243                     this, SLOT(onEventsFinished(KTp::PendingLoggerOperation*)));
0244 
0245             return;
0246         }
0247     }
0248 
0249     QList<KTp::Message> messages;
0250     const KTp::MessageContext ctx(d->account, d->textChannel);
0251     for (int i = qMax(allMessages.count() - d->scrollbackLength, 0) ; i < allMessages.count(); ++i) {
0252         const KTp::LogMessage message = allMessages[i];
0253         if (queuedMessageTokens.contains(message.token())) {
0254             continue;
0255         }
0256 
0257         messages << KTp::MessageProcessor::instance()->processIncomingMessage(message, ctx);
0258     }
0259 
0260     d->messagesCache.clear();
0261     d->datesCache.clear();
0262 
0263     Q_EMIT fetched(messages);
0264 }