Show invite menu in wlm chat window immediately
[kdenetwork.git] / kopete / plugins / statistics / statisticscontact.cpp
blob14de882ad2f14b111b787b5f8fe4394c67e5ca81
1 /*
2 statisticscontact.cpp
4 Copyright (c) 2003-2004 by Marc Cramdal <marc.cramdal@gmail.com>
6 Copyright (c) 2007 by the Kopete Developers <kopete-devel@kde.org>
8 *************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 *************************************************************************
18 #include <stdlib.h>
20 #include <q3valuelist.h>
22 #include <kdebug.h>
23 #include <klocale.h>
25 #include "kopetemetacontact.h"
26 #include "kopeteonlinestatus.h"
28 #include "statisticscontact.h"
29 #include "statisticsdb.h"
31 StatisticsContact::StatisticsContact(Kopete::MetaContact *mc, StatisticsDB *db) : m_metaContact(mc),m_db(db), m_oldStatus(Kopete::OnlineStatus::Unknown)
33 m_isChatWindowOpen = false;
34 m_oldStatusDateTime = QDateTime::currentDateTime();
36 // Last*Changed are always false at start
37 m_timeBetweenTwoMessagesChanged = false;
38 m_lastTalkChanged = false;
39 m_lastPresentChanged = false;
40 m_messageLengthChanged = false;
42 commonStatsCheck("timebetweentwomessages", m_timeBetweenTwoMessages, m_timeBetweenTwoMessagesOn, 0, -1);
43 commonStatsCheck("messagelength", m_messageLength, m_messageLengthOn, 0, 0);
45 // Check for last talk
46 QString lastTalk;
47 QString dummy = "";
48 commonStatsCheck("lasttalk", lastTalk, dummy);
49 if (lastTalk.isEmpty())
51 m_lastTalk.setTime_t(0);
52 m_lastTalkChanged = true;
54 else
55 m_lastTalk = QDateTime::fromString(lastTalk);
58 // Get last time a message was received
59 m_lastMessageReceived = QDateTime::currentDateTime();
62 // Check for lastPresent
63 QString lastPresent = "";
64 commonStatsCheck("lastpresent", lastPresent, dummy);
65 if (lastPresent.isEmpty())
67 m_lastPresent.setTime_t(0);
68 m_lastPresentChanged = true;
70 else
71 m_lastPresent = QDateTime::fromString(lastPresent);
74 /**
75 * \brief saves contact statistics
77 StatisticsContact::~StatisticsContact()
79 commonStatsSave("timebetweentwomessages",QString::number(m_timeBetweenTwoMessages),
80 QString::number(m_timeBetweenTwoMessagesOn), m_timeBetweenTwoMessagesChanged);
81 commonStatsSave("messagelength",QString::number(m_messageLength), QString::number(m_messageLengthOn), m_messageLengthChanged);
82 commonStatsSave("lasttalk", m_lastTalk.toString(), "", m_lastTalkChanged);
83 commonStatsSave("lastpresent", m_lastPresent.toString(), "", m_lastPresentChanged);
86 void StatisticsContact::commonStatsSave(const QString name, const QString statVar1, const QString statVar2, const bool statVarChanged)
88 // Only update the database if there was a change
89 if (!statVarChanged) return;
91 m_db->query(QString("UPDATE commonstats SET statvalue1 = '%1', statvalue2='%2'"
92 "WHERE statname LIKE '%3' AND metacontactid LIKE '%4';").arg(statVar1).arg(statVar2).arg(name).arg(metaContact()->metaContactId()));
96 void StatisticsContact::commonStatsCheck(const QString name, int& statVar1, int& statVar2, const int defaultValue1, const int defaultValue2)
98 QString a = QString::number(statVar1);
99 QString b = QString::number(statVar2);
101 commonStatsCheck(name, a, b, QString::number(defaultValue1), QString::number(defaultValue2));
103 statVar1 = a.toInt();
104 statVar2 = b.toInt();
107 void StatisticsContact::commonStatsCheck(const QString name, QString& statVar1, QString& statVar2, const QString defaultValue1, const QString defaultValue2)
109 QStringList buffer = m_db->query(QString("SELECT statvalue1,statvalue2 FROM commonstats WHERE statname LIKE '%1' AND metacontactid LIKE '%2';").arg(name, metaContact()->metaContactId()));
110 if (!buffer.isEmpty())
112 statVar1 = buffer[0];
113 statVar2 = buffer[1];
115 else
117 m_db->query(QString("INSERT INTO commonstats (metacontactid, statname, statvalue1, statvalue2) VALUES('%1', '%2', 0, 0);").arg(metaContact()->metaContactId(), name));
118 statVar1 = defaultValue1;
119 statVar2 = defaultValue2;
124 * \brief records information from the new message
126 * Currently it does :
127 * <ul>
128 * <li>Recalculate the average time between two messages
129 * It should only calculate this time if a chatwindow is open (sure, it isn't
130 * perfect, because we could let a chatwindow open a whole day but that's at this * time the nicest way, maybe we could check the time between the two last messages
131 * and if it is greater than, say, 10 min, do as there where no previous message)
132 * So we do this when the chatwindow is open. We don't set m_isChatWindowOpen to true
133 * when a new chatwindow is open, but when a new message arrives. However, we set it
134 * to false when the chatwindow is closed (see StatisticsPlugin::slotViewClosed).
136 * Then it is only a question of some calculations.
138 * <li>Recalculate the average message length
140 * <li>Change last-talk datetime
141 * </ul>
144 void StatisticsContact::newMessageReceived(Kopete::Message& m)
146 kDebug(14315) << "statistics: new message received";
147 QDateTime currentDateTime = QDateTime::currentDateTime();
149 if (m_timeBetweenTwoMessagesOn != -1 && m_isChatWindowOpen)
151 m_timeBetweenTwoMessages = (m_timeBetweenTwoMessages*m_timeBetweenTwoMessagesOn + m_lastMessageReceived.secsTo(currentDateTime))/(1 + m_timeBetweenTwoMessagesOn);
155 setIsChatWindowOpen(true);
157 m_timeBetweenTwoMessagesOn += 1;
158 m_lastMessageReceived = currentDateTime;
161 // Message length
162 m_messageLength= (m.plainBody().length() + m_messageLength * m_messageLengthOn)/(1 + m_messageLengthOn);
163 m_messageLengthOn++;
165 // Last talked
166 /// @todo do this in message sent too. So we need setLastTalk()
167 m_lastTalk = currentDateTime;
169 m_messageLengthChanged = true;
170 m_lastTalkChanged = true;
171 m_timeBetweenTwoMessagesChanged = true;
175 * \brief Update the database for this contact when required.
177 void StatisticsContact::onlineStatusChanged(Kopete::OnlineStatus::StatusType status)
179 QDateTime currentDateTime = QDateTime::currentDateTime();
181 /// We don't want to log if oldStatus is unknown
182 /// the change could not be a real one; see StatisticsPlugin::slotMySelfOnlineStatusChanged
183 if (m_oldStatus != Kopete::OnlineStatus::Unknown)
186 kDebug(14315) << "statistics - status change for "<< metaContact()->metaContactId() << " : "<< QString::number(m_oldStatus);
187 m_db->query(QString("INSERT INTO contactstatus "
188 "(metacontactid, status, datetimebegin, datetimeend) "
189 "VALUES('%1', '%2', '%3', '%4'" ");").arg(m_metaContact->metaContactId()).arg(Kopete::OnlineStatus::statusTypeToString(m_oldStatus)).arg(QString::number(m_oldStatusDateTime.toTime_t())).arg(QString::number(currentDateTime.toTime_t())));
192 if (m_oldStatus == Kopete::OnlineStatus::Online || m_oldStatus == Kopete::OnlineStatus::Away)
193 // If the last status was Online or Away, the last time contact was present is the time he goes offline
195 m_lastPresent = currentDateTime;
196 m_lastPresentChanged = true;
199 m_oldStatus = status;
200 m_oldStatusDateTime = currentDateTime;
204 bool StatisticsContact::wasStatus(QDateTime dt, Kopete::OnlineStatus::StatusType status)
206 QStringList values = m_db->query(QString("SELECT status, datetimebegin, datetimeend "
207 "FROM contactstatus WHERE metacontactid LIKE '%1' AND datetimebegin <= %2 AND datetimeend >= %3 "
208 "AND status LIKE '%4' "
209 "ORDER BY datetimebegin;"
210 ).arg(metaContact()->metaContactId()).arg(dt.toTime_t()).arg(dt.toTime_t()).arg(Kopete::OnlineStatus::statusTypeToString(status)));
212 if (!values.isEmpty()) return true;
214 return false;
217 QString StatisticsContact::statusAt(QDateTime dt)
219 QStringList values = m_db->query(QString("SELECT status, datetimebegin, datetimeend "
220 "FROM contactstatus WHERE metacontactid LIKE '%1' AND datetimebegin <= %2 AND datetimeend >= %3 "
221 "ORDER BY datetimebegin;"
222 ).arg(metaContact()->metaContactId()).arg(dt.toTime_t()).arg(dt.toTime_t()));
224 if (!values.isEmpty()) return Kopete::OnlineStatus(Kopete::OnlineStatus::statusStringToType(values[0])).description();
225 else return "";
228 QString StatisticsContact::mainStatusDate(const QDate& date)
230 QDateTime dt1(date, QTime(0,0,0));
231 QDateTime dt2(date.addDays(1), QTime(0,0,0));
232 kDebug(14315) << "dt1:" << dt1.toString() << " dt2:" << dt2.toString();
233 QString request = QString("SELECT status, datetimebegin, datetimeend, metacontactid "
234 "FROM contactstatus WHERE metacontactid = '%1' AND "
235 "(datetimebegin >= %2 AND datetimebegin <= %3 OR "
236 "datetimeend >= %4 AND datetimeend <= %5) "
237 "ORDER BY datetimebegin;"
238 ).arg(metaContact()->metaContactId()).arg(dt1.toTime_t()).arg(dt2.toTime_t()).arg(dt1.toTime_t()).arg(dt2.toTime_t());
239 kDebug(14315) << request;
240 QStringList values = m_db->query(request);
242 unsigned int online = 0, offline = 0, away = 0;
243 for(int i=0; i<values.count(); i+=4)
245 unsigned int datetimebegin = values[i+1].toInt(), datetimeend = values[i+2].toInt();
246 kDebug(14315) << "statistics: id "<< values[i+3]<< " status " << values[i] << " datetimeend " << QString::number(datetimeend) << " datetimebegin " << QString::number(datetimebegin);
247 if (datetimebegin <= dt1.toTime_t()) datetimebegin = dt1.toTime_t();
248 if (datetimeend >= dt2.toTime_t()) datetimeend = dt2.toTime_t();
252 if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Online))
253 online += datetimeend - datetimebegin;
254 else if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Away))
255 away += datetimeend - datetimebegin;
256 else if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Offline))
257 offline += datetimeend - datetimebegin;
260 if (online > away && online > offline) return i18n("Online");
261 else if (away > online && away > offline) return i18n("Away");
262 else if (offline > online && offline > away) return i18n("Offline");
264 return "";
267 // QDateTime StatisticsContact::nextOfflineEvent()
268 // {
269 // return nextEvent(Kopete::OnlineStatus::Offline);
270 // }
272 // QDateTime StatisticsContact::nextOnlineEvent()
273 // {
274 // return nextEvent(Kopete::OnlineStatus::Online);
275 // }
277 // QDateTime StatisticsContact::nextEvent(const Kopete::OnlineStatus::StatusType& status)
278 // {
280 // }
282 QList<QTime> StatisticsContact::mainEvents(const Kopete::OnlineStatus::StatusType& status)
284 QStringList buffer;
285 QList<QTime> mainEvents;
288 QDateTime currentDateTime = QDateTime::currentDateTime();
289 buffer = m_db->query(QString("SELECT datetimebegin, datetimeend, status FROM contactstatus WHERE metacontactid LIKE '%1' ORDER BY datetimebegin").arg(metaContact()->metaContactId()));
292 // Only select the events for which the previous is not Unknown AND the status is status.
293 QStringList values;
294 for (int i=0; i<buffer.count(); i += 3)
296 if (buffer[i+2] == Kopete::OnlineStatus::statusTypeToString(status)
297 && abs(buffer[i+1].toInt()-buffer[i].toInt()) > 120)
299 values.push_back(buffer[i]);
303 // No entries for this contact ...
304 if (!values.count()) return mainEvents;
306 // First we compute the average number of events/day : avEventsPerDay;
307 int avEventsPerDay = 0;
308 QDateTime dt1, dt2;
309 dt1.setTime_t(values[0].toInt());
310 dt2.setTime_t(values[values.count()-1].toInt());
312 avEventsPerDay = qRound((double)values.count()/(double)dt1.daysTo(dt2));
313 kDebug(14315) << "statistics: average events per day : " <<avEventsPerDay;
315 // We want to work on hours
316 QList<int> hoursValues;
317 for (int i=0; i<values.count(); i++)
319 QDateTime dt;
320 dt.setTime_t(values[i].toInt());
321 hoursValues.push_back(QTime(0, 0, 0).secsTo(dt.time()));
324 // Sort the list
325 //qSort(hoursValues);
327 // Then we put some centroids (centroids in [0..24[)
328 QList<int> centroids;
329 int incr=qRound((double)hoursValues.count()/(double)avEventsPerDay);
330 incr = incr ? incr : 1;
331 for (int i=0; i<hoursValues.count(); i+=incr)
333 centroids.push_back(hoursValues[i]);
334 kDebug(14315) << "statistics: add a centroid : " << centroids[centroids.count()-1];
338 // We need to compute the centroids
339 centroids = computeCentroids(centroids, hoursValues);
341 // Convert to QDateTime
342 for (int i=0; i<centroids.count(); i++)
344 kDebug(14315) << "statistics: new centroid : " << centroids[i];
346 QTime dt(0, 0, 0);
347 dt = dt.addSecs(centroids[i]);
348 mainEvents.push_back(dt);
352 return mainEvents;
355 QList<int> StatisticsContact::computeCentroids(const QList<int>& centroids, const QList<int>& values)
357 kDebug(14315) << "statistics: enter compute centroids";
359 QList<int> whichCentroid; // whichCentroid[i] = j <=> values[i] has centroid j for closest one
360 QList<int> newCentroids;
362 QList<int>::ConstIterator it = values.begin();
363 QList<int>::ConstIterator end = values.end();
364 for ( ; it != end; ++it )
365 // Iterates over the values. For each one we need to get the closest centroid.
367 int value = *it;
368 int distanceToNearestCentroid = abs(centroids[0]-value);
369 int nearestCentroid = 0;
370 for (int j=1; j<centroids.count(); j++)
372 if (abs(centroids[j]-value) < distanceToNearestCentroid)
374 distanceToNearestCentroid = abs(centroids[j]-value);
375 nearestCentroid = j;
378 whichCentroid.push_back(nearestCentroid);
381 // Recompute centroids
382 newCentroids = centroids;
384 for (int i=0; i<newCentroids.count(); i++)
386 kDebug(14315) << "statistics: compute new centroids"<< i;
387 int weight = 0;
388 for (int j=0; j<values.count(); j++)
390 int value = values[j];
391 if (whichCentroid[j] == i)
393 newCentroids[i] = qRound((double)(value + newCentroids[i]*weight)/(double)(weight + 1));
394 weight++;
402 // Should we recompute or are we OK ?
403 int dist = 0;
404 for (int i=0; i < newCentroids.count(); i++)
405 dist += abs(newCentroids[i]-centroids[i]);
407 if (dist > 10)
408 return computeCentroids(newCentroids, values);
409 else
412 return newCentroids;