1 /***************************************************************************
2 * Copyright (C) 2005-2018 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"
26 #include "clientbacklogmanager.h"
29 #include "networkmodel.h"
31 class ProcessBufferEvent : public QEvent
34 inline ProcessBufferEvent() : QEvent(QEvent::User) {}
38 MessageModel::MessageModel(QObject *parent)
39 : QAbstractItemModel(parent)
41 QDateTime now = QDateTime::currentDateTime();
42 now.setTimeSpec(Qt::UTC);
43 _nextDayChange.setTimeSpec(Qt::UTC);
44 _nextDayChange.setMSecsSinceEpoch(
45 ((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, SIGNAL(timeout()), this, SLOT(changeOfDay()));
53 QVariant MessageModel::data(const QModelIndex &index, int role) const
55 int row = index.row(); 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);
67 bool MessageModel::setData(const QModelIndex &index, const QVariant &value, int role)
69 int row = index.row();
70 if (row < 0 || row >= messageCount())
73 if (messageItemAt(row)->setData(index.column(), value, role)) {
74 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);
95 void MessageModel::insertMessages(const QList<Message> &msglist)
97 if (msglist.isEmpty())
100 if (_messageBuffer.isEmpty()) {
101 int processedMsgs = insertMessagesGracefully(msglist);
102 int remainingMsgs = msglist.count() - processedMsgs;
103 if (remainingMsgs > 0) {
104 if (msglist.first().msgId() < msglist.last().msgId()) {
105 // in Order - we have just successfully processed "processedMsg" messages from the end of the list
106 _messageBuffer = msglist.mid(0, remainingMsgs);
109 _messageBuffer = msglist.mid(processedMsgs);
111 qSort(_messageBuffer);
112 QCoreApplication::postEvent(this, new ProcessBufferEvent());
116 _messageBuffer << msglist;
117 qSort(_messageBuffer);
122 void MessageModel::insertMessageGroup(const QList<Message> &msglist)
124 Q_ASSERT(!msglist.isEmpty()); // the msglist can be assumed to be non empty
125 // int last = msglist.count() - 1;
126 // Q_ASSERT(0 == last || msglist.at(0).msgId() != msglist.at(last).msgId() || msglist.at(last).type() == Message::DayChange);
127 int start = indexForId(msglist.first().msgId());
128 int end = start + msglist.count() - 1;
129 Message dayChangeMsg;
132 // check if the preceeding msg is a daychange message and if so if
133 // we have to drop or relocate it at the end of this chunk
134 int prevIdx = start - 1;
135 if (messageItemAt(prevIdx)->msgType() == Message::DayChange
136 && messageItemAt(prevIdx)->timestamp() > msglist.at(0).timestamp()) {
137 beginRemoveRows(QModelIndex(), prevIdx, prevIdx);
138 Message oldDayChangeMsg = takeMessageAt(prevIdx);
139 if (msglist.last().timestamp() < oldDayChangeMsg.timestamp()) {
140 // we have to reinsert it with a changed msgId
141 dayChangeMsg = oldDayChangeMsg;
142 dayChangeMsg.setMsgId(msglist.last().msgId());
151 if (!dayChangeMsg.isValid() && start < messageCount()) {
152 // if(!dayChangeItem && start < _messageList.count()) {
153 // check if we need to insert a daychange message at the end of the this group
155 // if this assert triggers then indexForId() would have found a spot right before a DayChangeMsg
156 // this should never happen as daychange messages share the msgId with the preceeding message
157 Q_ASSERT(messageItemAt(start)->msgType() != Message::DayChange);
158 QDateTime nextTs = messageItemAt(start)->timestamp();
159 QDateTime prevTs = msglist.last().timestamp();
160 nextTs.setTimeSpec(Qt::UTC);
161 prevTs.setTimeSpec(Qt::UTC);
162 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
163 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
164 if (nextDay != prevDay) {
165 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
166 nextTs.setTimeSpec(Qt::LocalTime);
167 dayChangeMsg = Message::ChangeOfDay(nextTs);
168 dayChangeMsg.setMsgId(msglist.last().msgId());
172 if (dayChangeMsg.isValid())
175 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < msglist.first().msgId());
176 Q_ASSERT(start == messageCount() || messageItemAt(start)->msgId() > msglist.last().msgId());
177 beginInsertRows(QModelIndex(), start, end);
178 insertMessages__(start, msglist);
179 if (dayChangeMsg.isValid())
180 insertMessage__(start + msglist.count(), dayChangeMsg);
183 Q_ASSERT(start == end || messageItemAt(start)->msgId() != messageItemAt(end)->msgId() || messageItemAt(end)->msgType() == Message::DayChange);
184 Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < messageItemAt(start)->msgId());
185 Q_ASSERT(end + 1 == messageCount() || messageItemAt(end)->msgId() < messageItemAt(end + 1)->msgId());
189 int MessageModel::insertMessagesGracefully(const QList<Message> &msglist)
191 /* short description:
192 * 1) first we check where the message with the highest msgId from msglist would be inserted
193 * 2) check that position for dupe
194 * 3) determine the messageId of the preceeding msg
195 * 4) insert as many msgs from msglist with with msgId larger then the just determined id
196 * those messages are automatically less then the msg of the position we just determined in 1)
198 bool inOrder = (msglist.first().msgId() < msglist.last().msgId());
199 // depending on the order we have to traverse from the front to the back or vice versa
201 QList<Message> grouplist;
204 int processedMsgs = 1; // we know the list isn't empty, so we at least process one message
206 bool fastForward = false;
207 QList<Message>::const_iterator iter;
209 iter = msglist.constEnd();
210 --iter; // this op is safe as we've allready passed an empty check
213 iter = msglist.constBegin();
216 idx = indexForId((*iter).msgId());
217 if (idx < messageCount())
218 dupeId = messageItemAt(idx)->msgId();
220 // we always compare to the previous entry...
221 // if there isn't, we can fastforward to the top
223 minId = messageItemAt(idx - 1)->msgId();
227 if ((*iter).msgId() != dupeId) {
229 dupeId = (*iter).msgId();
236 while (iter != msglist.constBegin()) {
239 if (!fastForward && (*iter).msgId() <= minId)
243 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
244 idx = indexForId((*iter).msgId());
245 if (idx >= 0 && !messagesIsEmpty())
246 dupeId = messageItemAt(idx)->msgId();
248 if ((*iter).msgId() != dupeId) {
249 if (!grouplist.isEmpty()) {
250 QDateTime nextTs = grouplist.value(0).timestamp();
251 QDateTime prevTs = (*iter).timestamp();
252 nextTs.setTimeSpec(Qt::UTC);
253 prevTs.setTimeSpec(Qt::UTC);
254 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
255 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
256 if (nextDay != prevDay) {
257 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
258 nextTs.setTimeSpec(Qt::LocalTime);
259 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
260 dayChangeMsg.setMsgId((*iter).msgId());
261 grouplist.prepend(dayChangeMsg);
264 dupeId = (*iter).msgId();
265 grouplist.prepend(*iter);
270 while (iter != msglist.constEnd()) {
271 if (!fastForward && (*iter).msgId() <= minId)
275 if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId
276 idx = indexForId((*iter).msgId());
277 if (idx >= 0 && !messagesIsEmpty())
278 dupeId = messageItemAt(idx)->msgId();
280 if ((*iter).msgId() != dupeId) {
281 if (!grouplist.isEmpty()) {
282 QDateTime nextTs = grouplist.value(0).timestamp();
283 QDateTime prevTs = (*iter).timestamp();
284 nextTs.setTimeSpec(Qt::UTC);
285 prevTs.setTimeSpec(Qt::UTC);
286 qint64 nextDay = nextTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
287 qint64 prevDay = prevTs.toMSecsSinceEpoch() / DAY_IN_MSECS;
288 if (nextDay != prevDay) {
289 nextTs.setMSecsSinceEpoch(nextDay * DAY_IN_MSECS);
290 nextTs.setTimeSpec(Qt::LocalTime);
291 Message dayChangeMsg = Message::ChangeOfDay(nextTs);
292 dayChangeMsg.setMsgId((*iter).msgId());
293 grouplist.prepend(dayChangeMsg);
296 dupeId = (*iter).msgId();
297 grouplist.prepend(*iter);
303 if (!grouplist.isEmpty())
304 insertMessageGroup(grouplist);
305 return processedMsgs;
309 void MessageModel::customEvent(QEvent *event)
311 if (event->type() != QEvent::User)
316 if (_messageBuffer.isEmpty())
319 int processedMsgs = insertMessagesGracefully(_messageBuffer);
320 int remainingMsgs = _messageBuffer.count() - processedMsgs;
322 QList<Message>::iterator removeStart = _messageBuffer.begin() + remainingMsgs;
323 QList<Message>::iterator removeEnd = _messageBuffer.end();
324 _messageBuffer.erase(removeStart, removeEnd);
325 if (!_messageBuffer.isEmpty())
326 QCoreApplication::postEvent(this, new ProcessBufferEvent());
330 void MessageModel::clear()
332 _messagesWaiting.clear();
333 if (rowCount() > 0) {
334 beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
341 // returns index of msg with given Id or of the next message after that (i.e., the index where we'd insert this msg)
342 int MessageModel::indexForId(MsgId id)
344 if (messagesIsEmpty() || id <= messageItemAt(0)->msgId())
347 if (id > lastMessageItem()->msgId())
348 return messageCount();
351 int start = 0; int end = messageCount() - 1;
353 if (end - start == 1)
355 int pivot = (end + start) / 2;
356 if (id <= messageItemAt(pivot)->msgId()) end = pivot;
362 void MessageModel::changeOfDay()
364 _dayChangeTimer.setInterval(DAY_IN_MSECS);
365 if (!messagesIsEmpty()) {
366 int idx = messageCount();
367 while (idx > 0 && messageItemAt(idx - 1)->timestamp() > _nextDayChange) {
370 beginInsertRows(QModelIndex(), idx, idx);
371 Message dayChangeMsg = Message::ChangeOfDay(_nextDayChange);
372 dayChangeMsg.setMsgId(messageItemAt(idx - 1)->msgId());
373 insertMessage__(idx, dayChangeMsg);
376 _nextDayChange = _nextDayChange.addMSecs(DAY_IN_MSECS);
380 void MessageModel::insertErrorMessage(BufferInfo bufferInfo, const QString &errorString)
382 int idx = messageCount();
383 beginInsertRows(QModelIndex(), idx, idx);
384 Message msg(bufferInfo, Message::Error, errorString);
385 if (!messagesIsEmpty())
386 msg.setMsgId(messageItemAt(idx-1)->msgId());
389 insertMessage__(idx, msg);
394 void MessageModel::requestBacklog(BufferId bufferId)
396 if (_messagesWaiting.contains(bufferId))
399 BacklogSettings backlogSettings;
400 int requestCount = backlogSettings.dynamicBacklogAmount();
402 for (int i = 0; i < messageCount(); i++) {
403 if (messageItemAt(i)->bufferId() == bufferId) {
404 _messagesWaiting[bufferId] = requestCount;
405 Client::backlogManager()->emitMessagesRequested(tr("Requesting %1 messages from backlog for buffer %2:%3")
407 .arg(Client::networkModel()->networkName(bufferId))
408 .arg(Client::networkModel()->bufferName(bufferId)));
409 Client::backlogManager()->requestBacklog(bufferId, -1, messageItemAt(i)->msgId(), requestCount);
416 void MessageModel::messagesReceived(BufferId bufferId, int count)
418 if (!_messagesWaiting.contains(bufferId))
421 _messagesWaiting[bufferId] -= count;
422 if (_messagesWaiting[bufferId] <= 0) {
423 _messagesWaiting.remove(bufferId);
424 emit finishedBacklogFetch(bufferId);
429 void MessageModel::buffersPermanentlyMerged(BufferId bufferId1, BufferId bufferId2)
431 for (int i = 0; i < messageCount(); i++) {
432 if (messageItemAt(i)->bufferId() == bufferId2) {
433 messageItemAt(i)->setBufferId(bufferId1);
434 QModelIndex idx = index(i, 0);
435 emit dataChanged(idx, idx);
441 // ========================================
443 // ========================================
444 QVariant MessageModelItem::data(int column, int role) const
446 if (column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn)
450 case MessageModel::MessageRole:
451 return QVariant::fromValue<Message>(message());
452 case MessageModel::MsgIdRole:
453 return QVariant::fromValue<MsgId>(msgId());
454 case MessageModel::BufferIdRole:
455 return QVariant::fromValue<BufferId>(bufferId());
456 case MessageModel::TypeRole:
458 case MessageModel::FlagsRole:
459 return (int)msgFlags();
460 case MessageModel::TimestampRole:
462 case MessageModel::RedirectedToRole:
463 return qVariantFromValue<BufferId>(_redirectedTo);
470 bool MessageModelItem::setData(int column, const QVariant &value, int role)
475 case MessageModel::RedirectedToRole:
476 _redirectedTo = value.value<BufferId>();
485 bool MessageModelItem::lessThan(const MessageModelItem *m1, const MessageModelItem *m2)
487 return (*m1) < (*m2);
491 bool MessageModelItem::operator<(const MessageModelItem &other) const
493 return msgId() < other.msgId();
497 bool MessageModelItem::operator==(const MessageModelItem &other) const
499 return msgId() == other.msgId();
503 bool MessageModelItem::operator>(const MessageModelItem &other) const
505 return msgId() > other.msgId();
509 QDebug operator<<(QDebug dbg, const MessageModelItem &msgItem)
511 dbg.nospace() << qPrintable(QString("MessageModelItem(MsgId:")) << msgItem.msgId()
512 << qPrintable(QString(",")) << msgItem.timestamp()
513 << qPrintable(QString(", Type:")) << msgItem.msgType()
514 << qPrintable(QString(", Flags:")) << msgItem.msgFlags() << qPrintable(QString(")"))
515 << msgItem.data(1, Qt::DisplayRole).toString() << ":" << msgItem.data(2, Qt::DisplayRole).toString();