1 /***************************************************************************
2 * Copyright (C) 2005-2019 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"
25 #include "backlogsettings.h"
27 #include "clientbacklogmanager.h"
29 #include "networkmodel.h"
31 class ProcessBufferEvent : public QEvent
34 inline ProcessBufferEvent()
35 : QEvent(QEvent::User)
39 MessageModel::MessageModel(QObject* parent)
40 : QAbstractItemModel(parent)
42 QDateTime now = QDateTime::currentDateTime();
43 now.setTimeSpec(Qt::UTC);
44 _nextDayChange.setTimeSpec(Qt::UTC);
45 _nextDayChange.setMSecsSinceEpoch(((now.toMSecsSinceEpoch() / DAY_IN_MSECS) + 1) * DAY_IN_MSECS);
46 _nextDayChange.setTimeSpec(Qt::LocalTime);
47 _dayChangeTimer.setInterval(QDateTime::currentDateTime().secsTo(_nextDayChange) * 1000);
48 _dayChangeTimer.start();
49 connect(&_dayChangeTimer, &QTimer::timeout, this, &MessageModel::changeOfDay);
52 QVariant MessageModel::data(const QModelIndex& index, int role) const
54 int row = index.row();
55 int column = index.column();
56 if (row < 0 || row >= messageCount() || column < 0)
59 if (role == ColumnTypeRole)
62 return messageItemAt(row)->data(index.column(), role);
63 // return _messageList[row]->data(index.column(), role);
66 bool MessageModel::setData(const QModelIndex& index, const QVariant& value, int role)
68 int row = index.row();
69 if (row < 0 || row >= messageCount())
72 if (messageItemAt(row)->setData(index.column(), value, role)) {
73 emit dataChanged(index, index);
79 bool MessageModel::insertMessage(const Message& msg, bool fakeMsg)
81 MsgId id = msg.msgId();
82 int idx = indexForId(id);
83 if (!fakeMsg && idx < messageCount()) { // check for duplicate
84 if (messageItemAt(idx)->msgId() == id)
88 insertMessageGroup(QList<Message>() << msg);
92 void MessageModel::insertMessages(const QList<Message>& msglist)
94 if (msglist.isEmpty())
97 if (_messageBuffer.isEmpty()) {
98 int processedMsgs = insertMessagesGracefully(msglist);
99 int remainingMsgs = msglist.count() - processedMsgs;
100 if (remainingMsgs > 0) {
101 if (msglist.first().msgId() < msglist.last().msgId()) {
102 // in Order - we have just successfully processed "processedMsg" messages from the end of the list
103 _messageBuffer = msglist.mid(0, remainingMsgs);
106 _messageBuffer = msglist.mid(processedMsgs);
108 qSort(_messageBuffer);
109 QCoreApplication::postEvent(this, new ProcessBufferEvent());
113 _messageBuffer << msglist;
114 qSort(_messageBuffer);
118 void MessageModel::insertMessageGroup(const QList<Message>& msglist)
120 Q_ASSERT(!msglist.isEmpty()); // the msglist can be assumed to be non empty
121 // int last = msglist.count() - 1;
122 // Q_ASSERT(0 == last || msglist.at(0).msgId() != msglist.at(last).msgId() || msglist.at(last).type() == Message::DayChange);
123 int start = indexForId(msglist.first().msgId());
124 int end = start + msglist.count() - 1;
125 Message dayChangeMsg;
128 // check if the preceeding msg is a daychange message and if so if
129 // we have to drop or relocate it at the end of this chunk
130 int prevIdx = start - 1;
131 if (messageItemAt(prevIdx)->msgType() == Message::DayChange && messageItemAt(prevIdx)->timestamp() > msglist.at(0).timestamp()) {
132 beginRemoveRows(QModelIndex(), prevIdx, prevIdx);
133 Message oldDayChangeMsg = takeMessageAt(prevIdx);
134 if (msglist.last().timestamp() < oldDayChangeMsg.timestamp()) {
135 // we have to reinsert it with a changed msgId
136 dayChangeMsg = oldDayChangeMsg;
137 dayChangeMsg.setMsgId(msglist.last().msgId());
146 if (!dayChangeMsg.isValid() && start < messageCount()) {
147 // if(!dayChangeItem && start < _messageList.count()) {
148 // check if we need to insert a daychange message at the end of the this group
150 // if this assert triggers then indexForId() would have found a spot right before a DayChangeMsg
151 // this should never happen as daychange messages share the msgId with the preceeding message
152 Q_ASSERT(messageItemAt(start)->msgType() != Message::DayChange);
153 QDateTime nextTs = messageItemAt(start)->timestamp();
154 QDateTime prevTs = msglist.last().timestamp();
155 nextTs.setTimeSpec(Qt::UTC);
156 prevTs.setTimeSpec(Qt::UTC);
157 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
158 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
159 if (nextDay != prevDay) {
160 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
161 nextTs.setTimeSpec(Qt::LocalTime);
162 dayChangeMsg = Message::ChangeOfDay(nextTs);
163 dayChangeMsg.setMsgId(msglist.last().msgId());
167 if (dayChangeMsg.isValid())
170 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < msglist.first().msgId());
171 Q_ASSERT(start == messageCount() || messageItemAt(start)->msgId() > msglist.last().msgId());
172 beginInsertRows(QModelIndex(), start, end);
173 insertMessages__(start, msglist);
174 if (dayChangeMsg.isValid())
175 insertMessage__(start + msglist.count(), dayChangeMsg);
178 Q_ASSERT(start == end || messageItemAt(start)->msgId() != messageItemAt(end)->msgId()
179 || messageItemAt(end)->msgType() == Message::DayChange);
180 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < messageItemAt(start)->msgId());
181 Q_ASSERT(end + 1 == messageCount() || messageItemAt(end)->msgId() < messageItemAt(end + 1)->msgId());
184 int MessageModel::insertMessagesGracefully(const QList<Message>& msglist)
186 /* short description:
187 * 1) first we check where the message with the highest msgId from msglist would be inserted
188 * 2) check that position for dupe
189 * 3) determine the messageId of the preceeding msg
190 * 4) insert as many msgs from msglist with with msgId larger then the just determined id
191 * those messages are automatically less then the msg of the position we just determined in 1)
193 bool inOrder = (msglist.first().msgId() < msglist.last().msgId());
194 // depending on the order we have to traverse from the front to the back or vice versa
196 QList<Message> grouplist;
199 int processedMsgs = 1; // we know the list isn't empty, so we at least process one message
201 bool fastForward = false;
202 QList<Message>::const_iterator iter;
204 iter = msglist.constEnd();
205 --iter; // this op is safe as we've allready passed an empty check
208 iter = msglist.constBegin();
211 idx = indexForId((*iter).msgId());
212 if (idx < messageCount())
213 dupeId = messageItemAt(idx)->msgId();
215 // we always compare to the previous entry...
216 // if there isn't, we can fastforward to the top
218 minId = messageItemAt(idx - 1)->msgId();
222 if ((*iter).msgId() != dupeId) {
224 dupeId = (*iter).msgId();
231 while (iter != msglist.constBegin()) {
234 if (!fastForward && (*iter).msgId() <= minId)
238 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
239 idx = indexForId((*iter).msgId());
240 if (idx >= 0 && !messagesIsEmpty())
241 dupeId = messageItemAt(idx)->msgId();
243 if ((*iter).msgId() != dupeId) {
244 if (!grouplist.isEmpty()) {
245 QDateTime nextTs = grouplist.value(0).timestamp();
246 QDateTime prevTs = (*iter).timestamp();
247 nextTs.setTimeSpec(Qt::UTC);
248 prevTs.setTimeSpec(Qt::UTC);
249 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
250 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
251 if (nextDay != prevDay) {
252 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
253 nextTs.setTimeSpec(Qt::LocalTime);
254 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
255 dayChangeMsg.setMsgId((*iter).msgId());
256 grouplist.prepend(dayChangeMsg);
259 dupeId = (*iter).msgId();
260 grouplist.prepend(*iter);
265 while (iter != msglist.constEnd()) {
266 if (!fastForward && (*iter).msgId() <= minId)
270 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
271 idx = indexForId((*iter).msgId());
272 if (idx >= 0 && !messagesIsEmpty())
273 dupeId = messageItemAt(idx)->msgId();
275 if ((*iter).msgId() != dupeId) {
276 if (!grouplist.isEmpty()) {
277 QDateTime nextTs = grouplist.value(0).timestamp();
278 QDateTime prevTs = (*iter).timestamp();
279 nextTs.setTimeSpec(Qt::UTC);
280 prevTs.setTimeSpec(Qt::UTC);
281 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
282 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
283 if (nextDay != prevDay) {
284 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
285 nextTs.setTimeSpec(Qt::LocalTime);
286 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
287 dayChangeMsg.setMsgId((*iter).msgId());
288 grouplist.prepend(dayChangeMsg);
291 dupeId = (*iter).msgId();
292 grouplist.prepend(*iter);
298 if (!grouplist.isEmpty())
299 insertMessageGroup(grouplist);
300 return processedMsgs;
303 void MessageModel::customEvent(QEvent* event)
305 if (event->type() != QEvent::User)
310 if (_messageBuffer.isEmpty())
313 int processedMsgs = insertMessagesGracefully(_messageBuffer);
314 int remainingMsgs = _messageBuffer.count() - processedMsgs;
316 QList<Message>::iterator removeStart = _messageBuffer.begin() + remainingMsgs;
317 QList<Message>::iterator removeEnd = _messageBuffer.end();
318 _messageBuffer.erase(removeStart, removeEnd);
319 if (!_messageBuffer.isEmpty())
320 QCoreApplication::postEvent(this, new ProcessBufferEvent());
323 void MessageModel::clear()
325 _messagesWaiting.clear();
326 if (rowCount() > 0) {
327 beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
333 // returns index of msg with given Id or of the next message after that (i.e., the index where we'd insert this msg)
334 int MessageModel::indexForId(MsgId id)
336 if (messagesIsEmpty() || id <= messageItemAt(0)->msgId())
339 if (id > lastMessageItem()->msgId())
340 return messageCount();
344 int end = messageCount() - 1;
346 if (end - start == 1)
348 int pivot = (end + start) / 2;
349 if (id <= messageItemAt(pivot)->msgId())
356 void MessageModel::changeOfDay()
358 _dayChangeTimer.setInterval(DAY_IN_MSECS);
359 if (!messagesIsEmpty()) {
360 int idx = messageCount();
361 while (idx > 0 && messageItemAt(idx - 1)->timestamp() > _nextDayChange) {
364 beginInsertRows(QModelIndex(), idx, idx);
365 Message dayChangeMsg = Message::ChangeOfDay(_nextDayChange);
366 dayChangeMsg.setMsgId(messageItemAt(idx - 1)->msgId());
367 insertMessage__(idx, dayChangeMsg);
370 _nextDayChange = _nextDayChange.addMSecs(DAY_IN_MSECS);
373 void MessageModel::insertErrorMessage(BufferInfo bufferInfo, const QString& errorString)
375 int idx = messageCount();
376 beginInsertRows(QModelIndex(), idx, idx);
377 Message msg(bufferInfo, Message::Error, errorString);
378 if (!messagesIsEmpty())
379 msg.setMsgId(messageItemAt(idx - 1)->msgId());
382 insertMessage__(idx, msg);
386 void MessageModel::requestBacklog(BufferId bufferId)
388 if (_messagesWaiting.contains(bufferId))
391 BacklogSettings backlogSettings;
392 int requestCount = backlogSettings.dynamicBacklogAmount();
394 for (int i = 0; i < messageCount(); i++) {
395 if (messageItemAt(i)->bufferId() == bufferId) {
396 _messagesWaiting[bufferId] = requestCount;
397 Client::backlogManager()->emitMessagesRequested(tr("Requesting %1 messages from backlog for buffer %2:%3")
399 .arg(Client::networkModel()->networkName(bufferId))
400 .arg(Client::networkModel()->bufferName(bufferId)));
401 Client::backlogManager()->requestBacklog(bufferId, -1, messageItemAt(i)->msgId(), requestCount);
407 void MessageModel::messagesReceived(BufferId bufferId, int count)
409 if (!_messagesWaiting.contains(bufferId))
412 _messagesWaiting[bufferId] -= count;
413 if (_messagesWaiting[bufferId] <= 0) {
414 _messagesWaiting.remove(bufferId);
415 emit finishedBacklogFetch(bufferId);
419 void MessageModel::buffersPermanentlyMerged(BufferId bufferId1, BufferId bufferId2)
421 for (int i = 0; i < messageCount(); i++) {
422 if (messageItemAt(i)->bufferId() == bufferId2) {
423 messageItemAt(i)->setBufferId(bufferId1);
424 QModelIndex idx = index(i, 0);
425 emit dataChanged(idx, idx);
430 // ========================================
432 // ========================================
433 QVariant MessageModelItem::data(int column, int role) const
435 if (column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn)
439 case MessageModel::MessageRole:
440 return QVariant::fromValue<Message>(message());
441 case MessageModel::MsgIdRole:
442 return QVariant::fromValue<MsgId>(msgId());
443 case MessageModel::BufferIdRole:
444 return QVariant::fromValue<BufferId>(bufferId());
445 case MessageModel::TypeRole:
447 case MessageModel::FlagsRole:
448 return (int)msgFlags();
449 case MessageModel::TimestampRole:
451 case MessageModel::RedirectedToRole:
452 return qVariantFromValue<BufferId>(_redirectedTo);
458 bool MessageModelItem::setData(int column, const QVariant& value, int role)
463 case MessageModel::RedirectedToRole:
464 _redirectedTo = value.value<BufferId>();
472 bool MessageModelItem::lessThan(const MessageModelItem* m1, const MessageModelItem* m2)
474 return (*m1) < (*m2);
477 bool MessageModelItem::operator<(const MessageModelItem& other) const
479 return msgId() < other.msgId();
482 bool MessageModelItem::operator==(const MessageModelItem& other) const
484 return msgId() == other.msgId();
487 bool MessageModelItem::operator>(const MessageModelItem& other) const
489 return msgId() > other.msgId();
492 QDebug operator<<(QDebug dbg, const MessageModelItem& msgItem)
494 dbg.nospace() << qPrintable(QString("MessageModelItem(MsgId:")) << msgItem.msgId() << qPrintable(QString(",")) << msgItem.timestamp()
495 << qPrintable(QString(", Type:")) << msgItem.msgType() << qPrintable(QString(", Flags:")) << msgItem.msgFlags()
496 << qPrintable(QString(")")) << msgItem.data(1, Qt::DisplayRole).toString() << ":"
497 << msgItem.data(2, Qt::DisplayRole).toString();