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 *************************************************************************
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. *
15 *************************************************************************
20 #include <q3valuelist.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
48 commonStatsCheck("lasttalk", lastTalk
, dummy
);
49 if (lastTalk
.isEmpty())
51 m_lastTalk
.setTime_t(0);
52 m_lastTalkChanged
= true;
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;
71 m_lastPresent
= QDateTime::fromString(lastPresent
);
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];
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 :
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
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
;
162 m_messageLength
= (m
.plainBody().length() + m_messageLength
* m_messageLengthOn
)/(1 + m_messageLengthOn
);
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;
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();
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");
267 // QDateTime StatisticsContact::nextOfflineEvent()
269 // return nextEvent(Kopete::OnlineStatus::Offline);
272 // QDateTime StatisticsContact::nextOnlineEvent()
274 // return nextEvent(Kopete::OnlineStatus::Online);
277 // QDateTime StatisticsContact::nextEvent(const Kopete::OnlineStatus::StatusType& status)
282 QList
<QTime
> StatisticsContact::mainEvents(const Kopete::OnlineStatus::StatusType
& status
)
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.
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;
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
++)
320 dt
.setTime_t(values
[i
].toInt());
321 hoursValues
.push_back(QTime(0, 0, 0).secsTo(dt
.time()));
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
];
347 dt
= dt
.addSecs(centroids
[i
]);
348 mainEvents
.push_back(dt
);
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.
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
);
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
;
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));
402 // Should we recompute or are we OK ?
404 for (int i
=0; i
< newCentroids
.count(); i
++)
405 dist
+= abs(newCentroids
[i
]-centroids
[i
]);
408 return computeCentroids(newCentroids
, values
);