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