1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "messagemodel.h"
27 #include "backlogsettings.h"
29 #include "clientbacklogmanager.h"
31 #include "networkmodel.h"
33 class ProcessBufferEvent : public QEvent
36 inline ProcessBufferEvent()
37 : QEvent(QEvent::User)
41 MessageModel::MessageModel(QObject* parent)
42 : QAbstractItemModel(parent)
44 QDateTime now = QDateTime::currentDateTime();
45 now.setTimeSpec(Qt::UTC);
46 _nextDayChange.setTimeSpec(Qt::UTC);
47 _nextDayChange.setMSecsSinceEpoch(((now.toMSecsSinceEpoch() / DAY_IN_MSECS) + 1) * DAY_IN_MSECS);
48 _nextDayChange.setTimeSpec(Qt::LocalTime);
49 _dayChangeTimer.setInterval(QDateTime::currentDateTime().secsTo(_nextDayChange) * 1000);
50 _dayChangeTimer.start();
51 connect(&_dayChangeTimer, &QTimer::timeout, this, &MessageModel::changeOfDay);
54 QVariant MessageModel::data(const QModelIndex& index, int role) const
56 int row = index.row();
57 int column = index.column();
58 if (row < 0 || row >= messageCount() || column < 0)
61 if (role == ColumnTypeRole)
64 return messageItemAt(row)->data(index.column(), role);
65 // return _messageList[row]->data(index.column(), role);
68 bool MessageModel::setData(const QModelIndex& index, const QVariant& value, int role)
70 int row = index.row();
71 if (row < 0 || row >= messageCount())
74 if (messageItemAt(row)->setData(index.column(), value, role)) {
75 emit dataChanged(index, index);
81 bool MessageModel::insertMessage(const Message& msg, bool fakeMsg)
83 MsgId id = msg.msgId();
84 int idx = indexForId(id);
85 if (!fakeMsg && idx < messageCount()) { // check for duplicate
86 if (messageItemAt(idx)->msgId() == id)
90 insertMessageGroup(QList<Message>() << msg);
94 void MessageModel::insertMessages(const QList<Message>& msglist)
96 if (msglist.isEmpty())
99 if (_messageBuffer.isEmpty()) {
100 int processedMsgs = insertMessagesGracefully(msglist);
101 int remainingMsgs = msglist.count() - processedMsgs;
102 if (remainingMsgs > 0) {
103 if (msglist.first().msgId() < msglist.last().msgId()) {
104 // in Order - we have just successfully processed "processedMsg" messages from the end of the list
105 _messageBuffer = msglist.mid(0, remainingMsgs);
108 _messageBuffer = msglist.mid(processedMsgs);
110 std::sort(_messageBuffer.begin(), _messageBuffer.end());
111 QCoreApplication::postEvent(this, new ProcessBufferEvent());
115 _messageBuffer << msglist;
116 std::sort(_messageBuffer.begin(), _messageBuffer.end());
120 void MessageModel::insertMessageGroup(const QList<Message>& msglist)
122 Q_ASSERT(!msglist.isEmpty()); // the msglist can be assumed to be non empty
123 // int last = msglist.count() - 1;
124 // Q_ASSERT(0 == last || msglist.at(0).msgId() != msglist.at(last).msgId() || msglist.at(last).type() == Message::DayChange);
125 int start = indexForId(msglist.first().msgId());
126 int end = start + msglist.count() - 1;
127 Message dayChangeMsg;
130 // check if the preceeding msg is a daychange message and if so if
131 // we have to drop or relocate it at the end of this chunk
132 int prevIdx = start - 1;
133 if (messageItemAt(prevIdx)->msgType() == Message::DayChange && messageItemAt(prevIdx)->timestamp() > msglist.at(0).timestamp()) {
134 beginRemoveRows(QModelIndex(), prevIdx, prevIdx);
135 Message oldDayChangeMsg = takeMessageAt(prevIdx);
136 if (msglist.last().timestamp() < oldDayChangeMsg.timestamp()) {
137 // we have to reinsert it with a changed msgId
138 dayChangeMsg = oldDayChangeMsg;
139 dayChangeMsg.setMsgId(msglist.last().msgId());
148 if (!dayChangeMsg.isValid() && start < messageCount()) {
149 // if(!dayChangeItem && start < _messageList.count()) {
150 // check if we need to insert a daychange message at the end of the this group
152 // if this assert triggers then indexForId() would have found a spot right before a DayChangeMsg
153 // this should never happen as daychange messages share the msgId with the preceeding message
154 Q_ASSERT(messageItemAt(start)->msgType() != Message::DayChange);
155 QDateTime nextTs = messageItemAt(start)->timestamp();
156 QDateTime prevTs = msglist.last().timestamp();
157 nextTs.setTimeSpec(Qt::UTC);
158 prevTs.setTimeSpec(Qt::UTC);
159 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
160 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
161 if (nextDay != prevDay) {
162 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
163 nextTs.setTimeSpec(Qt::LocalTime);
164 dayChangeMsg = Message::ChangeOfDay(nextTs);
165 dayChangeMsg.setMsgId(msglist.last().msgId());
169 if (dayChangeMsg.isValid())
172 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < msglist.first().msgId());
173 Q_ASSERT(start == messageCount() || messageItemAt(start)->msgId() > msglist.last().msgId());
174 beginInsertRows(QModelIndex(), start, end);
175 insertMessages__(start, msglist);
176 if (dayChangeMsg.isValid())
177 insertMessage__(start + msglist.count(), dayChangeMsg);
180 Q_ASSERT(start == end || messageItemAt(start)->msgId() != messageItemAt(end)->msgId()
181 || messageItemAt(end)->msgType() == Message::DayChange);
182 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < messageItemAt(start)->msgId());
183 Q_ASSERT(end + 1 == messageCount() || messageItemAt(end)->msgId() < messageItemAt(end + 1)->msgId());
186 int MessageModel::insertMessagesGracefully(const QList<Message>& msglist)
188 /* short description:
189 * 1) first we check where the message with the highest msgId from msglist would be inserted
190 * 2) check that position for dupe
191 * 3) determine the messageId of the preceeding msg
192 * 4) insert as many msgs from msglist with with msgId larger then the just determined id
193 * those messages are automatically less then the msg of the position we just determined in 1)
195 bool inOrder = (msglist.first().msgId() < msglist.last().msgId());
196 // depending on the order we have to traverse from the front to the back or vice versa
198 QList<Message> grouplist;
201 int processedMsgs = 1; // we know the list isn't empty, so we at least process one message
203 bool fastForward = false;
204 QList<Message>::const_iterator iter;
206 iter = msglist.constEnd();
207 --iter; // this op is safe as we've allready passed an empty check
210 iter = msglist.constBegin();
213 idx = indexForId((*iter).msgId());
214 if (idx < messageCount())
215 dupeId = messageItemAt(idx)->msgId();
217 // we always compare to the previous entry...
218 // if there isn't, we can fastforward to the top
220 minId = messageItemAt(idx - 1)->msgId();
224 if ((*iter).msgId() != dupeId) {
226 dupeId = (*iter).msgId();
233 while (iter != msglist.constBegin()) {
236 if (!fastForward && (*iter).msgId() <= minId)
240 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
241 idx = indexForId((*iter).msgId());
242 if (idx >= 0 && !messagesIsEmpty())
243 dupeId = messageItemAt(idx)->msgId();
245 if ((*iter).msgId() != dupeId) {
246 if (!grouplist.isEmpty()) {
247 QDateTime nextTs = grouplist.value(0).timestamp();
248 QDateTime prevTs = (*iter).timestamp();
249 nextTs.setTimeSpec(Qt::UTC);
250 prevTs.setTimeSpec(Qt::UTC);
251 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
252 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
253 if (nextDay != prevDay) {
254 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
255 nextTs.setTimeSpec(Qt::LocalTime);
256 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
257 dayChangeMsg.setMsgId((*iter).msgId());
258 grouplist.prepend(dayChangeMsg);
261 dupeId = (*iter).msgId();
262 grouplist.prepend(*iter);
267 while (iter != msglist.constEnd()) {
268 if (!fastForward && (*iter).msgId() <= minId)
272 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
273 idx = indexForId((*iter).msgId());
274 if (idx >= 0 && !messagesIsEmpty())
275 dupeId = messageItemAt(idx)->msgId();
277 if ((*iter).msgId() != dupeId) {
278 if (!grouplist.isEmpty()) {
279 QDateTime nextTs = grouplist.value(0).timestamp();
280 QDateTime prevTs = (*iter).timestamp();
281 nextTs.setTimeSpec(Qt::UTC);
282 prevTs.setTimeSpec(Qt::UTC);
283 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
284 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
285 if (nextDay != prevDay) {
286 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
287 nextTs.setTimeSpec(Qt::LocalTime);
288 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
289 dayChangeMsg.setMsgId((*iter).msgId());
290 grouplist.prepend(dayChangeMsg);
293 dupeId = (*iter).msgId();
294 grouplist.prepend(*iter);
300 if (!grouplist.isEmpty())
301 insertMessageGroup(grouplist);
302 return processedMsgs;
305 void MessageModel::customEvent(QEvent* event)
307 if (event->type() != QEvent::User)
312 if (_messageBuffer.isEmpty())
315 int processedMsgs = insertMessagesGracefully(_messageBuffer);
316 int remainingMsgs = _messageBuffer.count() - processedMsgs;
318 QList<Message>::iterator removeStart = _messageBuffer.begin() + remainingMsgs;
319 QList<Message>::iterator removeEnd = _messageBuffer.end();
320 _messageBuffer.erase(removeStart, removeEnd);
321 if (!_messageBuffer.isEmpty())
322 QCoreApplication::postEvent(this, new ProcessBufferEvent());
325 void MessageModel::clear()
327 _messagesWaiting.clear();
328 if (rowCount() > 0) {
329 beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
335 // returns index of msg with given Id or of the next message after that (i.e., the index where we'd insert this msg)
336 int MessageModel::indexForId(MsgId id)
338 if (messagesIsEmpty() || id <= messageItemAt(0)->msgId())
341 if (id > lastMessageItem()->msgId())
342 return messageCount();
346 int end = messageCount() - 1;
348 if (end - start == 1)
350 int pivot = (end + start) / 2;
351 if (id <= messageItemAt(pivot)->msgId())
358 void MessageModel::changeOfDay()
360 _dayChangeTimer.setInterval(DAY_IN_MSECS);
361 if (!messagesIsEmpty()) {
362 int idx = messageCount();
363 while (idx > 0 && messageItemAt(idx - 1)->timestamp() > _nextDayChange) {
366 beginInsertRows(QModelIndex(), idx, idx);
367 Message dayChangeMsg = Message::ChangeOfDay(_nextDayChange);
368 dayChangeMsg.setMsgId(messageItemAt(idx - 1)->msgId());
369 insertMessage__(idx, dayChangeMsg);
372 _nextDayChange = _nextDayChange.addMSecs(DAY_IN_MSECS);
375 void MessageModel::insertErrorMessage(BufferInfo bufferInfo, const QString& errorString)
377 int idx = messageCount();
378 beginInsertRows(QModelIndex(), idx, idx);
379 Message msg(bufferInfo, Message::Error, errorString);
380 if (!messagesIsEmpty())
381 msg.setMsgId(messageItemAt(idx - 1)->msgId());
384 insertMessage__(idx, msg);
388 void MessageModel::requestBacklog(BufferId bufferId)
390 if (_messagesWaiting.contains(bufferId))
393 BacklogSettings backlogSettings;
394 int requestCount = backlogSettings.dynamicBacklogAmount();
396 for (int i = 0; i < messageCount(); i++) {
397 if (messageItemAt(i)->bufferId() == bufferId) {
398 _messagesWaiting[bufferId] = requestCount;
399 Client::backlogManager()->emitMessagesRequested(tr("Requesting %1 messages from backlog for buffer %2:%3")
401 .arg(Client::networkModel()->networkName(bufferId))
402 .arg(Client::networkModel()->bufferName(bufferId)));
403 Client::backlogManager()->requestBacklog(bufferId, -1, messageItemAt(i)->msgId(), requestCount);
409 void MessageModel::messagesReceived(BufferId bufferId, int count)
411 if (!_messagesWaiting.contains(bufferId))
414 _messagesWaiting[bufferId] -= count;
415 if (_messagesWaiting[bufferId] <= 0) {
416 _messagesWaiting.remove(bufferId);
417 emit finishedBacklogFetch(bufferId);
421 void MessageModel::buffersPermanentlyMerged(BufferId bufferId1, BufferId bufferId2)
423 for (int i = 0; i < messageCount(); i++) {
424 if (messageItemAt(i)->bufferId() == bufferId2) {
425 messageItemAt(i)->setBufferId(bufferId1);
426 QModelIndex idx = index(i, 0);
427 emit dataChanged(idx, idx);
432 // ========================================
434 // ========================================
435 QVariant MessageModelItem::data(int column, int role) const
437 if (column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn)
441 case MessageModel::MessageRole:
442 return QVariant::fromValue(message());
443 case MessageModel::MsgIdRole:
444 return QVariant::fromValue(msgId());
445 case MessageModel::BufferIdRole:
446 return QVariant::fromValue(bufferId());
447 case MessageModel::TypeRole:
449 case MessageModel::FlagsRole:
450 return (int)msgFlags();
451 case MessageModel::TimestampRole:
453 case MessageModel::RedirectedToRole:
454 return QVariant::fromValue(_redirectedTo);
460 bool MessageModelItem::setData(int column, const QVariant& value, int role)
465 case MessageModel::RedirectedToRole:
466 _redirectedTo = value.value<BufferId>();
474 bool MessageModelItem::lessThan(const MessageModelItem* m1, const MessageModelItem* m2)
476 return (*m1) < (*m2);
479 bool MessageModelItem::operator<(const MessageModelItem& other) const
481 return msgId() < other.msgId();
484 bool MessageModelItem::operator==(const MessageModelItem& other) const
486 return msgId() == other.msgId();
489 bool MessageModelItem::operator>(const MessageModelItem& other) const
491 return msgId() > other.msgId();
494 QDebug operator<<(QDebug dbg, const MessageModelItem& msgItem)
496 dbg.nospace() << qPrintable(QString("MessageModelItem(MsgId:")) << msgItem.msgId() << qPrintable(QString(",")) << msgItem.timestamp()
497 << qPrintable(QString(", Type:")) << msgItem.msgType() << qPrintable(QString(", Flags:")) << msgItem.msgFlags()
498 << qPrintable(QString(")")) << msgItem.data(1, Qt::DisplayRole).toString() << ":"
499 << msgItem.data(2, Qt::DisplayRole).toString();