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 }