1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 by the Quassel Project * |
3 | * devel@quassel-irc.org * |
4 | * * |
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. * |
9 | * * |
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. * |
14 | * * |
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 | ***************************************************************************/ |
20 | |
21 | #include "messagemodel.h" |
22 | |
23 | #include <QEvent> |
24 | |
25 | #include "backlogsettings.h" |
26 | #include "clientbacklogmanager.h" |
27 | #include "client.h" |
28 | #include "message.h" |
29 | #include "networkmodel.h" |
30 | |
31 | class ProcessBufferEvent : public QEvent |
32 | { |
33 | public: |
34 | inline ProcessBufferEvent() : QEvent(QEvent::User) {} |
35 | }; |
36 | |
37 | |
38 | MessageModel::MessageModel(QObject *parent) |
39 | : QAbstractItemModel(parent) |
40 | { |
41 | QDateTime now = QDateTime::currentDateTime(); |
42 | now.setTimeSpec(Qt::UTC); |
43 | _nextDayChange.setTimeSpec(Qt::UTC); |
44 | _nextDayChange.setTime_t(((now.toTime_t() / 86400) + 1) * 86400); |
45 | _nextDayChange.setTimeSpec(Qt::LocalTime); |
46 | _dayChangeTimer.setInterval(QDateTime::currentDateTime().secsTo(_nextDayChange) * 1000); |
47 | _dayChangeTimer.start(); |
48 | connect(&_dayChangeTimer, SIGNAL(timeout()), this, SLOT(changeOfDay())); |
49 | } |
50 | |
51 | |
52 | QVariant MessageModel::data(const QModelIndex &index, int role) const |
53 | { |
54 | int row = index.row(); int column = index.column(); |
55 | if (row < 0 || row >= messageCount() || column < 0) |
56 | return QVariant(); |
57 | |
58 | if (role == ColumnTypeRole) |
59 | return column; |
60 | |
61 | return messageItemAt(row)->data(index.column(), role); |
62 | // return _messageList[row]->data(index.column(), role); |
63 | } |
64 | |
65 | |
66 | bool MessageModel::setData(const QModelIndex &index, const QVariant &value, int role) |
67 | { |
68 | int row = index.row(); |
69 | if (row < 0 || row >= messageCount()) |
70 | return false; |
71 | |
72 | if (messageItemAt(row)->setData(index.column(), value, role)) { |
73 | emit dataChanged(index, index); |
74 | return true; |
75 | } |
76 | return false; |
77 | } |
78 | |
79 | |
80 | bool MessageModel::insertMessage(const Message &msg, bool fakeMsg) |
81 | { |
82 | MsgId id = msg.msgId(); |
83 | int idx = indexForId(id); |
84 | if (!fakeMsg && idx < messageCount()) { // check for duplicate |
85 | if (messageItemAt(idx)->msgId() == id) |
86 | return false; |
87 | } |
88 | |
89 | insertMessageGroup(QList<Message>() << msg); |
90 | return true; |
91 | } |
92 | |
93 | |
94 | void MessageModel::insertMessages(const QList<Message> &msglist) |
95 | { |
96 | if (msglist.isEmpty()) |
97 | return; |
98 | |
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); |
106 | } |
107 | else { |
108 | _messageBuffer = msglist.mid(processedMsgs); |
109 | } |
110 | qSort(_messageBuffer); |
111 | QCoreApplication::postEvent(this, new ProcessBufferEvent()); |
112 | } |
113 | } |
114 | else { |
115 | _messageBuffer << msglist; |
116 | qSort(_messageBuffer); |
117 | } |
118 | } |
119 | |
120 | |
121 | void MessageModel::insertMessageGroup(const QList<Message> &msglist) |
122 | { |
123 | Q_ASSERT(!msglist.isEmpty()); // the msglist can be assumed to be non empty |
124 | // int last = msglist.count() - 1; |
125 | // Q_ASSERT(0 == last || msglist.at(0).msgId() != msglist.at(last).msgId() || msglist.at(last).type() == Message::DayChange); |
126 | int start = indexForId(msglist.first().msgId()); |
127 | int end = start + msglist.count() - 1; |
128 | Message dayChangeMsg; |
129 | |
130 | if (start > 0) { |
131 | // check if the preceeding msg is a daychange message and if so if |
132 | // we have to drop or relocate it at the end of this chunk |
133 | int prevIdx = start - 1; |
134 | if (messageItemAt(prevIdx)->msgType() == Message::DayChange |
135 | && messageItemAt(prevIdx)->timestamp() > msglist.at(0).timestamp()) { |
136 | beginRemoveRows(QModelIndex(), prevIdx, prevIdx); |
137 | Message oldDayChangeMsg = takeMessageAt(prevIdx); |
138 | if (msglist.last().timestamp() < oldDayChangeMsg.timestamp()) { |
139 | // we have to reinsert it with a changed msgId |
140 | dayChangeMsg = oldDayChangeMsg; |
141 | dayChangeMsg.setMsgId(msglist.last().msgId()); |
142 | } |
143 | endRemoveRows(); |
144 | |
145 | start--; |
146 | end--; |
147 | } |
148 | } |
149 | |
150 | if (!dayChangeMsg.isValid() && start < messageCount()) { |
151 | // if(!dayChangeItem && start < _messageList.count()) { |
152 | // check if we need to insert a daychange message at the end of the this group |
153 | |
154 | // if this assert triggers then indexForId() would have found a spot right before a DayChangeMsg |
155 | // this should never happen as daychange messages share the msgId with the preceeding message |
156 | Q_ASSERT(messageItemAt(start)->msgType() != Message::DayChange); |
157 | QDateTime nextTs = messageItemAt(start)->timestamp(); |
158 | QDateTime prevTs = msglist.last().timestamp(); |
159 | nextTs.setTimeSpec(Qt::UTC); |
160 | prevTs.setTimeSpec(Qt::UTC); |
161 | uint nextDay = nextTs.toTime_t() / 86400; |
162 | uint prevDay = prevTs.toTime_t() / 86400; |
163 | if (nextDay != prevDay) { |
164 | nextTs.setTime_t(nextDay * 86400); |
165 | nextTs.setTimeSpec(Qt::LocalTime); |
166 | dayChangeMsg = Message::ChangeOfDay(nextTs); |
167 | dayChangeMsg.setMsgId(msglist.last().msgId()); |
168 | } |
169 | } |
170 | |
171 | if (dayChangeMsg.isValid()) |
172 | end++; |
173 | |
174 | Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < msglist.first().msgId()); |
175 | Q_ASSERT(start == messageCount() || messageItemAt(start)->msgId() > msglist.last().msgId()); |
176 | beginInsertRows(QModelIndex(), start, end); |
177 | insertMessages__(start, msglist); |
178 | if (dayChangeMsg.isValid()) |
179 | insertMessage__(start + msglist.count(), dayChangeMsg); |
180 | endInsertRows(); |
181 | |
182 | Q_ASSERT(start == end || messageItemAt(start)->msgId() != messageItemAt(end)->msgId() || messageItemAt(end)->msgType() == Message::DayChange); |
183 | Q_ASSERT(start == 0 || messageItemAt(start - 1)->msgId() < messageItemAt(start)->msgId()); |
184 | Q_ASSERT(end + 1 == messageCount() || messageItemAt(end)->msgId() < messageItemAt(end + 1)->msgId()); |
185 | } |
186 | |
187 | |
188 | int MessageModel::insertMessagesGracefully(const QList<Message> &msglist) |
189 | { |
190 | /* short description: |
191 | * 1) first we check where the message with the highest msgId from msglist would be inserted |
192 | * 2) check that position for dupe |
193 | * 3) determine the messageId of the preceeding msg |
194 | * 4) insert as many msgs from msglist with with msgId larger then the just determined id |
195 | * those messages are automatically less then the msg of the position we just determined in 1) |
196 | */ |
197 | bool inOrder = (msglist.first().msgId() < msglist.last().msgId()); |
198 | // depending on the order we have to traverse from the front to the back or vice versa |
199 | |
200 | QList<Message> grouplist; |
201 | MsgId minId; |
202 | MsgId dupeId; |
203 | int processedMsgs = 1; // we know the list isn't empty, so we at least process one message |
204 | int idx; |
205 | bool fastForward = false; |
206 | QList<Message>::const_iterator iter; |
207 | if (inOrder) { |
208 | iter = msglist.constEnd(); |
209 | iter--; // this op is safe as we've allready passed an empty check |
210 | } |
211 | else { |
212 | iter = msglist.constBegin(); |
213 | } |
214 | |
215 | idx = indexForId((*iter).msgId()); |
216 | if (idx < messageCount()) |
217 | dupeId = messageItemAt(idx)->msgId(); |
218 | |
219 | // we always compare to the previous entry... |
220 | // if there isn't, we can fastforward to the top |
221 | if (idx - 1 >= 0) |
222 | minId = messageItemAt(idx - 1)->msgId(); |
223 | else |
224 | fastForward = true; |
225 | |
226 | if ((*iter).msgId() != dupeId) { |
227 | grouplist << *iter; |
228 | dupeId = (*iter).msgId(); |
229 | } |
230 | |
231 | if (!inOrder) |
232 | iter++; |
233 | |
234 | if (inOrder) { |
235 | while (iter != msglist.constBegin()) { |
236 | iter--; |
237 | |
238 | if (!fastForward && (*iter).msgId() <= minId) |
239 | break; |
240 | processedMsgs++; |
241 | |
242 | if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId |
243 | idx = indexForId((*iter).msgId()); |
244 | if (idx >= 0 && !messagesIsEmpty()) |
245 | dupeId = messageItemAt(idx)->msgId(); |
246 | } |
247 | if ((*iter).msgId() != dupeId) { |
248 | if (!grouplist.isEmpty()) { |
249 | QDateTime nextTs = grouplist.value(0).timestamp(); |
250 | QDateTime prevTs = (*iter).timestamp(); |
251 | nextTs.setTimeSpec(Qt::UTC); |
252 | prevTs.setTimeSpec(Qt::UTC); |
253 | uint nextDay = nextTs.toTime_t() / 86400; |
254 | uint prevDay = prevTs.toTime_t() / 86400; |
255 | if (nextDay != prevDay) { |
256 | nextTs.setTime_t(nextDay * 86400); |
257 | nextTs.setTimeSpec(Qt::LocalTime); |
258 | Message dayChangeMsg = Message::ChangeOfDay(nextTs); |
259 | dayChangeMsg.setMsgId((*iter).msgId()); |
260 | grouplist.prepend(dayChangeMsg); |
261 | } |
262 | } |
263 | dupeId = (*iter).msgId(); |
264 | grouplist.prepend(*iter); |
265 | } |
266 | } |
267 | } |
268 | else { |
269 | while (iter != msglist.constEnd()) { |
270 | if (!fastForward && (*iter).msgId() <= minId) |
271 | break; |
272 | processedMsgs++; |
273 | |
274 | if (grouplist.isEmpty()) { // as long as we don't have a starting point, we have to update the dupeId |
275 | idx = indexForId((*iter).msgId()); |
276 | if (idx >= 0 && !messagesIsEmpty()) |
277 | dupeId = messageItemAt(idx)->msgId(); |
278 | } |
279 | if ((*iter).msgId() != dupeId) { |
280 | if (!grouplist.isEmpty()) { |
281 | QDateTime nextTs = grouplist.value(0).timestamp(); |
282 | QDateTime prevTs = (*iter).timestamp(); |
283 | nextTs.setTimeSpec(Qt::UTC); |
284 | prevTs.setTimeSpec(Qt::UTC); |
285 | uint nextDay = nextTs.toTime_t() / 86400; |
286 | uint prevDay = prevTs.toTime_t() / 86400; |
287 | if (nextDay != prevDay) { |
288 | nextTs.setTime_t(nextDay * 86400); |
289 | nextTs.setTimeSpec(Qt::LocalTime); |
290 | Message dayChangeMsg = Message::ChangeOfDay(nextTs); |
291 | dayChangeMsg.setMsgId((*iter).msgId()); |
292 | grouplist.prepend(dayChangeMsg); |
293 | } |
294 | } |
295 | dupeId = (*iter).msgId(); |
296 | grouplist.prepend(*iter); |
297 | } |
298 | iter++; |
299 | } |
300 | } |
301 | |
302 | if (!grouplist.isEmpty()) |
303 | insertMessageGroup(grouplist); |
304 | return processedMsgs; |
305 | } |
306 | |
307 | |
308 | void MessageModel::customEvent(QEvent *event) |
309 | { |
310 | if (event->type() != QEvent::User) |
311 | return; |
312 | |
313 | event->accept(); |
314 | |
315 | if (_messageBuffer.isEmpty()) |
316 | return; |
317 | |
318 | int processedMsgs = insertMessagesGracefully(_messageBuffer); |
319 | int remainingMsgs = _messageBuffer.count() - processedMsgs; |
320 | |
321 | QList<Message>::iterator removeStart = _messageBuffer.begin() + remainingMsgs; |
322 | QList<Message>::iterator removeEnd = _messageBuffer.end(); |
323 | _messageBuffer.erase(removeStart, removeEnd); |
324 | if (!_messageBuffer.isEmpty()) |
325 | QCoreApplication::postEvent(this, new ProcessBufferEvent()); |
326 | } |
327 | |
328 | |
329 | void MessageModel::clear() |
330 | { |
331 | _messagesWaiting.clear(); |
332 | if (rowCount() > 0) { |
333 | beginRemoveRows(QModelIndex(), 0, rowCount() - 1); |
334 | removeAllMessages(); |
335 | endRemoveRows(); |
336 | } |
337 | } |
338 | |
339 | |
340 | // returns index of msg with given Id or of the next message after that (i.e., the index where we'd insert this msg) |
341 | int MessageModel::indexForId(MsgId id) |
342 | { |
343 | if (messagesIsEmpty() || id <= messageItemAt(0)->msgId()) |
344 | return 0; |
345 | |
346 | if (id > lastMessageItem()->msgId()) |
347 | return messageCount(); |
348 | |
349 | // binary search |
350 | int start = 0; int end = messageCount() - 1; |
351 | while (1) { |
352 | if (end - start == 1) |
353 | return end; |
354 | int pivot = (end + start) / 2; |
355 | if (id <= messageItemAt(pivot)->msgId()) end = pivot; |
356 | else start = pivot; |
357 | } |
358 | } |
359 | |
360 | |
361 | void MessageModel::changeOfDay() |
362 | { |
363 | _dayChangeTimer.setInterval(86400000); |
364 | if (!messagesIsEmpty()) { |
365 | int idx = messageCount(); |
366 | while (idx > 0 && messageItemAt(idx - 1)->timestamp() > _nextDayChange) { |
367 | idx--; |
368 | } |
369 | beginInsertRows(QModelIndex(), idx, idx); |
370 | Message dayChangeMsg = Message::ChangeOfDay(_nextDayChange); |
371 | dayChangeMsg.setMsgId(messageItemAt(idx - 1)->msgId()); |
372 | insertMessage__(idx, dayChangeMsg); |
373 | endInsertRows(); |
374 | } |
375 | _nextDayChange = _nextDayChange.addSecs(86400); |
376 | } |
377 | |
378 | |
379 | void MessageModel::insertErrorMessage(BufferInfo bufferInfo, const QString &errorString) |
380 | { |
381 | int idx = messageCount(); |
382 | beginInsertRows(QModelIndex(), idx, idx); |
383 | Message msg(bufferInfo, Message::Error, errorString); |
384 | if (!messagesIsEmpty()) |
385 | msg.setMsgId(messageItemAt(idx-1)->msgId()); |
386 | else |
387 | msg.setMsgId(0); |
388 | insertMessage__(idx, msg); |
389 | endInsertRows(); |
390 | } |
391 | |
392 | |
393 | void MessageModel::requestBacklog(BufferId bufferId) |
394 | { |
395 | if (_messagesWaiting.contains(bufferId)) |
396 | return; |
397 | |
398 | BacklogSettings backlogSettings; |
399 | int requestCount = backlogSettings.dynamicBacklogAmount(); |
400 | |
401 | for (int i = 0; i < messageCount(); i++) { |
402 | if (messageItemAt(i)->bufferId() == bufferId) { |
403 | _messagesWaiting[bufferId] = requestCount; |
404 | Client::backlogManager()->emitMessagesRequested(tr("Requesting %1 messages from backlog for buffer %2:%3" ) |
405 | .arg(requestCount) |
406 | .arg(Client::networkModel()->networkName(bufferId)) |
407 | .arg(Client::networkModel()->bufferName(bufferId))); |
408 | Client::backlogManager()->requestBacklog(bufferId, -1, messageItemAt(i)->msgId(), requestCount); |
409 | return; |
410 | } |
411 | } |
412 | } |
413 | |
414 | |
415 | void MessageModel::messagesReceived(BufferId bufferId, int count) |
416 | { |
417 | if (!_messagesWaiting.contains(bufferId)) |
418 | return; |
419 | |
420 | _messagesWaiting[bufferId] -= count; |
421 | if (_messagesWaiting[bufferId] <= 0) { |
422 | _messagesWaiting.remove(bufferId); |
423 | emit finishedBacklogFetch(bufferId); |
424 | } |
425 | } |
426 | |
427 | |
428 | void MessageModel::buffersPermanentlyMerged(BufferId bufferId1, BufferId bufferId2) |
429 | { |
430 | for (int i = 0; i < messageCount(); i++) { |
431 | if (messageItemAt(i)->bufferId() == bufferId2) { |
432 | messageItemAt(i)->setBufferId(bufferId1); |
433 | QModelIndex idx = index(i, 0); |
434 | emit dataChanged(idx, idx); |
435 | } |
436 | } |
437 | } |
438 | |
439 | |
440 | // ======================================== |
441 | // MessageModelItem |
442 | // ======================================== |
443 | QVariant MessageModelItem::data(int column, int role) const |
444 | { |
445 | if (column < MessageModel::TimestampColumn || column > MessageModel::ContentsColumn) |
446 | return QVariant(); |
447 | |
448 | switch (role) { |
449 | case MessageModel::MessageRole: |
450 | return QVariant::fromValue<Message>(message()); |
451 | case MessageModel::MsgIdRole: |
452 | return QVariant::fromValue<MsgId>(msgId()); |
453 | case MessageModel::BufferIdRole: |
454 | return QVariant::fromValue<BufferId>(bufferId()); |
455 | case MessageModel::TypeRole: |
456 | return msgType(); |
457 | case MessageModel::FlagsRole: |
458 | return (int)msgFlags(); |
459 | case MessageModel::TimestampRole: |
460 | return timestamp(); |
461 | case MessageModel::RedirectedToRole: |
462 | return qVariantFromValue<BufferId>(_redirectedTo); |
463 | default: |
464 | return QVariant(); |
465 | } |
466 | } |
467 | |
468 | |
469 | bool MessageModelItem::setData(int column, const QVariant &value, int role) |
470 | { |
471 | Q_UNUSED(column); |
472 | |
473 | switch (role) { |
474 | case MessageModel::RedirectedToRole: |
475 | _redirectedTo = value.value<BufferId>(); |
476 | return true; |
477 | default: |
478 | return false; |
479 | } |
480 | } |
481 | |
482 | |
483 | // Stuff for later |
484 | bool MessageModelItem::lessThan(const MessageModelItem *m1, const MessageModelItem *m2) |
485 | { |
486 | return (*m1) < (*m2); |
487 | } |
488 | |
489 | |
490 | bool MessageModelItem::operator<(const MessageModelItem &other) const |
491 | { |
492 | return msgId() < other.msgId(); |
493 | } |
494 | |
495 | |
496 | bool MessageModelItem::operator==(const MessageModelItem &other) const |
497 | { |
498 | return msgId() == other.msgId(); |
499 | } |
500 | |
501 | |
502 | bool MessageModelItem::operator>(const MessageModelItem &other) const |
503 | { |
504 | return msgId() > other.msgId(); |
505 | } |
506 | |
507 | |
508 | QDebug operator<<(QDebug dbg, const MessageModelItem &msgItem) |
509 | { |
510 | dbg.nospace() << qPrintable(QString("MessageModelItem(MsgId:" )) << msgItem.msgId() |
511 | << qPrintable(QString("," )) << msgItem.timestamp() |
512 | << qPrintable(QString(", Type:" )) << msgItem.msgType() |
513 | << qPrintable(QString(", Flags:" )) << msgItem.msgFlags() << qPrintable(QString(")" )) |
514 | << msgItem.data(1, Qt::DisplayRole).toString() << ":" << msgItem.data(2, Qt::DisplayRole).toString(); |
515 | return dbg; |
516 | } |
517 | |