summaryrefslogtreecommitdiffstats
path: root/kopete/plugins/statistics/statisticscontact.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/plugins/statistics/statisticscontact.cpp')
-rw-r--r--kopete/plugins/statistics/statisticscontact.cpp515
1 files changed, 515 insertions, 0 deletions
diff --git a/kopete/plugins/statistics/statisticscontact.cpp b/kopete/plugins/statistics/statisticscontact.cpp
new file mode 100644
index 00000000..e9068fe5
--- /dev/null
+++ b/kopete/plugins/statistics/statisticscontact.cpp
@@ -0,0 +1,515 @@
+/*
+ statisticscontact.cpp
+
+ Copyright (c) 2003-2004 by Marc Cramdal <marc.cramdal@gmail.com>
+
+
+ *************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ *************************************************************************
+*/
+
+#include <stdlib.h>
+
+#include <qvaluelist.h>
+#include <quuid.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "kopetemetacontact.h"
+#include "kopeteonlinestatus.h"
+
+#include "statisticscontact.h"
+#include "statisticsdb.h"
+
+StatisticsContact::StatisticsContact(Kopete::MetaContact *mc, StatisticsDB *db) : m_metaContact(mc),m_db(db), m_oldStatus(Kopete::OnlineStatus::Unknown)
+{
+ m_isChatWindowOpen = false;
+ m_oldStatusDateTime = QDateTime::currentDateTime();
+
+ // Last*Changed are always false at start
+ m_timeBetweenTwoMessagesChanged = false;
+ m_lastTalkChanged = false;
+ m_lastPresentChanged = false;
+ m_messageLengthChanged = false;
+}
+
+/**
+ * \brief saves contact statistics
+ */
+StatisticsContact::~StatisticsContact()
+{
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ commonStatsSave("timebetweentwomessages",QString::number(m_timeBetweenTwoMessages),
+ QString::number(m_timeBetweenTwoMessagesOn), m_timeBetweenTwoMessagesChanged);
+ commonStatsSave("messagelength",QString::number(m_messageLength), QString::number(m_messageLengthOn), m_messageLengthChanged);
+ commonStatsSave("lasttalk", m_lastTalk.toString(), "", m_lastTalkChanged);
+ commonStatsSave("lastpresent", m_lastPresent.toString(), "", m_lastPresentChanged);
+}
+
+void StatisticsContact::initialize(Kopete::Contact *c)
+{
+ // Generate statisticsContactId or get it from database
+ QStringList buffer = m_db->query(QString("SELECT statisticid FROM contacts "
+ "WHERE contactid LIKE '%1';"
+ ).arg(c->contactId()));
+
+ if (buffer.isEmpty())
+ {
+ // Check if we don't have old data
+ if ( !c->metaContact()->metaContactId().isEmpty() &&
+ !m_db->query(QString("SELECT metacontactid FROM commonstats "
+ "WHERE metacontactid LIKE '%1';"
+ ).arg(c->metaContact()->metaContactId())).isEmpty())
+ {
+ // Use old style id
+ m_statisticsContactId = c->metaContact()->metaContactId();
+ }
+ else
+ {
+ // Create new id
+ m_statisticsContactId = QUuid::createUuid().toString();
+ }
+
+ // Assign contactId to m_statisticsContactId
+ m_db->query(QString("INSERT INTO contacts (statisticid, contactid) VALUES('%1', '%2');"
+ ).arg(m_statisticsContactId).arg(c->contactId()));
+ }
+ else
+ {
+ m_statisticsContactId = buffer[0];
+ }
+
+ kdDebug() << k_funcinfo << " m_statisticsContactId: " << m_statisticsContactId << endl;
+
+ commonStatsCheck("timebetweentwomessages", m_timeBetweenTwoMessages, m_timeBetweenTwoMessagesOn, 0, -1);
+ commonStatsCheck("messagelength", m_messageLength, m_messageLengthOn, 0, 0);
+
+ // Check for last talk
+ QString lastTalk;
+ QString dummy = "";
+ commonStatsCheck("lasttalk", lastTalk, dummy);
+ if (lastTalk.isEmpty())
+ {
+ m_lastTalk.setTime_t(0);
+ m_lastTalkChanged = true;
+ }
+ else
+ m_lastTalk = QDateTime::fromString(lastTalk);
+
+
+ // Get last time a message was received
+ m_lastMessageReceived = QDateTime::currentDateTime();
+
+
+ // Check for lastPresent
+ QString lastPresent = "";
+ commonStatsCheck("lastpresent", lastPresent, dummy);
+ if (lastPresent.isEmpty())
+ {
+ m_lastPresent.setTime_t(0);
+ m_lastPresentChanged = true;
+ }
+ else
+ m_lastPresent = QDateTime::fromString(lastPresent);
+}
+
+void StatisticsContact::contactAdded( Kopete::Contact *c )
+{
+ if ( !m_statisticsContactId.isEmpty() )
+ {
+ // Check if contact is allready in database if not add it
+ if (m_db->query(QString("SELECT id FROM contacts "
+ "WHERE statisticid LIKE '%1' AND contactid LIKE '%2';"
+ ).arg(m_statisticsContactId).arg(c->contactId())).isEmpty())
+ {
+ // Assign contactId to m_statisticsContactId
+ m_db->query(QString("INSERT INTO contacts (statisticid, contactid) VALUES('%1', '%2');"
+ ).arg(m_statisticsContactId).arg(c->contactId()));
+ }
+ kdDebug() << k_funcinfo << " m_statisticsContactId: " << m_statisticsContactId << endl;
+ }
+ else
+ {
+ // This is first contact, we need to initialize this object
+ initialize(c);
+ }
+}
+
+void StatisticsContact::contactRemoved( Kopete::Contact *c )
+{
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ kdDebug() << k_funcinfo << " m_statisticsContactId: " << m_statisticsContactId << endl;
+ m_db->query(QString("DELETE FROM contacts WHERE statisticid LIKE '%1' AND contactid LIKE '%2';"
+ ).arg(m_statisticsContactId).arg(c->contactId()));
+}
+
+void StatisticsContact::removeFromDB()
+{
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ kdDebug() << k_funcinfo << " m_statisticsContactId: " << m_statisticsContactId << endl;
+ m_db->query(QString("DELETE FROM contacts WHERE statisticid LIKE '%1';").arg(m_statisticsContactId));
+ m_db->query(QString("DELETE FROM contactstatus WHERE metacontactid LIKE '%1';").arg(m_statisticsContactId));
+ m_db->query(QString("DELETE FROM commonstats WHERE metacontactid LIKE '%1';").arg(m_statisticsContactId));
+
+ m_statisticsContactId = QString::null;
+}
+
+void StatisticsContact::commonStatsSave(const QString name, const QString statVar1, const QString statVar2, const bool statVarChanged)
+{
+ // Only update the database if there was a change
+ if (!statVarChanged) return;
+
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ m_db->query(QString("UPDATE commonstats SET statvalue1 = '%1', statvalue2='%2'"
+ "WHERE statname LIKE '%3' AND metacontactid LIKE '%4';").arg(statVar1).arg(statVar2).arg(name).arg(m_statisticsContactId));
+
+}
+
+void StatisticsContact::commonStatsCheck(const QString name, int& statVar1, int& statVar2, const int defaultValue1, const int defaultValue2)
+{
+ QString a = QString::number(statVar1);
+ QString b = QString::number(statVar2);
+
+ commonStatsCheck(name, a, b, QString::number(defaultValue1), QString::number(defaultValue2));
+
+ statVar1 = a.toInt();
+ statVar2 = b.toInt();
+}
+
+void StatisticsContact::commonStatsCheck(const QString name, QString& statVar1, QString& statVar2, const QString defaultValue1, const QString defaultValue2)
+{
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ QStringList buffer = m_db->query(QString("SELECT statvalue1,statvalue2 FROM commonstats WHERE statname LIKE '%1' AND metacontactid LIKE '%2';").arg(name, m_statisticsContactId));
+ if (!buffer.isEmpty())
+ {
+ statVar1 = buffer[0];
+ statVar2 = buffer[1];
+ }
+ else
+ {
+ m_db->query(QString("INSERT INTO commonstats (metacontactid, statname, statvalue1, statvalue2) VALUES('%1', '%2', 0, 0);").arg(m_statisticsContactId, name));
+ statVar1 = defaultValue1;
+ statVar2 = defaultValue2;
+ }
+}
+
+/**
+ * \brief records informations from the new message
+ *
+ * Currently it does :
+ * <ul>
+ * <li>Recalculate the average time between two messages
+ * It should only calculate this time if a chatwindow is open (sure, it isn't
+ * 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
+ * and if it is greater than, say, 10 min, do as there where no previous message)
+ * So we do this when the chatwindow is open. We don't set m_isChatWindowOpen to true
+ * when a new chatwindow is open, but when a new message arrives. However, we set it
+ * to false when the chatwindow is closed (see StatisticsPlugin::slotViewClosed).
+ *
+ * Then it is only a question of some calculations.
+ *
+ * <li>Recalculate the average message lenght
+ *
+ * <li>Change last-talk datetime
+ * </ul>
+ *
+ */
+void StatisticsContact::newMessageReceived(Kopete::Message& m)
+{
+ kdDebug() << "statistics: new message received" << endl;
+ QDateTime currentDateTime = QDateTime::currentDateTime();
+
+ if (m_timeBetweenTwoMessagesOn != -1 && m_isChatWindowOpen)
+ {
+ m_timeBetweenTwoMessages = (m_timeBetweenTwoMessages*m_timeBetweenTwoMessagesOn + m_lastMessageReceived.secsTo(currentDateTime))/(1 + m_timeBetweenTwoMessagesOn);
+
+ }
+
+ setIsChatWindowOpen(true);
+
+ m_timeBetweenTwoMessagesOn += 1;
+ m_lastMessageReceived = currentDateTime;
+
+
+ // Message lenght
+ m_messageLength= (m.plainBody().length() + m_messageLength * m_messageLengthOn)/(1 + m_messageLengthOn);
+ m_messageLengthOn++;
+
+ // Last talked
+ /// @todo do this in message sent too. So we need setLastTalk()
+ m_lastTalk = currentDateTime;
+
+ m_messageLengthChanged = true;
+ m_lastTalkChanged = true;
+ m_timeBetweenTwoMessagesChanged = true;
+}
+
+/**
+ * \brief Update the database for this contact when required.
+ */
+void StatisticsContact::onlineStatusChanged(Kopete::OnlineStatus::StatusType status)
+{
+ if (m_statisticsContactId.isEmpty())
+ return;
+
+ QDateTime currentDateTime = QDateTime::currentDateTime();
+
+ /// We don't want to log if oldStatus is unknown
+ /// the change could not be a real one; see StatisticsPlugin::slotMySelfOnlineStatusChanged
+ if (m_oldStatus != Kopete::OnlineStatus::Unknown)
+ {
+
+ kdDebug() << "statistics - status change for "<< metaContact()->metaContactId() << " : "<< QString::number(m_oldStatus) << endl;
+ m_db->query(QString("INSERT INTO contactstatus "
+ "(metacontactid, status, datetimebegin, datetimeend) "
+ "VALUES('%1', '%2', '%3', '%4'" ");").arg(m_statisticsContactId).arg(Kopete::OnlineStatus::statusTypeToString(m_oldStatus)).arg(QString::number(m_oldStatusDateTime.toTime_t())).arg(QString::number(currentDateTime.toTime_t())));
+ }
+
+ if (m_oldStatus == Kopete::OnlineStatus::Online || m_oldStatus == Kopete::OnlineStatus::Away)
+ // If the last status was Online or Away, the last time contact was present is the time he goes offline
+ {
+ m_lastPresent = currentDateTime;
+ m_lastPresentChanged = true;
+ }
+
+ m_oldStatus = status;
+ m_oldStatusDateTime = currentDateTime;
+
+}
+
+bool StatisticsContact::wasStatus(QDateTime dt, Kopete::OnlineStatus::StatusType status)
+{
+ if (m_statisticsContactId.isEmpty())
+ return false;
+
+ QStringList values = m_db->query(QString("SELECT status, datetimebegin, datetimeend "
+ "FROM contactstatus WHERE metacontactid LIKE '%1' AND datetimebegin <= %2 AND datetimeend >= %3 "
+ "AND status LIKE '%4' "
+ "ORDER BY datetimebegin;"
+ ).arg(m_statisticsContactId).arg(dt.toTime_t()).arg(dt.toTime_t()).arg(Kopete::OnlineStatus::statusTypeToString(status)));
+
+ if (!values.isEmpty()) return true;
+
+ return false;
+}
+
+QString StatisticsContact::statusAt(QDateTime dt)
+{
+ if (m_statisticsContactId.isEmpty())
+ return "";
+
+ QStringList values = m_db->query(QString("SELECT status, datetimebegin, datetimeend "
+ "FROM contactstatus WHERE metacontactid LIKE '%1' AND datetimebegin <= %2 AND datetimeend >= %3 "
+ "ORDER BY datetimebegin;"
+ ).arg(m_statisticsContactId).arg(dt.toTime_t()).arg(dt.toTime_t()));
+
+ if (!values.isEmpty()) return Kopete::OnlineStatus(Kopete::OnlineStatus::statusStringToType(values[0])).description();
+ else return "";
+}
+
+QString StatisticsContact::mainStatusDate(const QDate& date)
+{
+ if (m_statisticsContactId.isEmpty())
+ return "";
+
+ QDateTime dt1(date, QTime(0,0,0));
+ QDateTime dt2(date.addDays(1), QTime(0,0,0));
+ kdDebug() << "dt1:" << dt1.toString() << " dt2:" << dt2.toString() << endl;
+ QString request = QString("SELECT status, datetimebegin, datetimeend, metacontactid "
+ "FROM contactstatus WHERE metacontactid = '%1' AND "
+ "(datetimebegin >= %2 AND datetimebegin <= %3 OR "
+ "datetimeend >= %4 AND datetimeend <= %5) "
+ "ORDER BY datetimebegin;"
+ ).arg(m_statisticsContactId).arg(dt1.toTime_t()).arg(dt2.toTime_t()).arg(dt1.toTime_t()).arg(dt2.toTime_t());
+ kdDebug() << request << endl;
+ QStringList values = m_db->query(request);
+
+ unsigned int online = 0, offline = 0, away = 0;
+ for(uint i=0; i<values.count(); i+=4)
+ {
+ unsigned int datetimebegin = values[i+1].toInt(), datetimeend = values[i+2].toInt();
+ kdDebug() << "statistics: id "<< values[i+3]<< " status " << values[i] << " datetimeend " << QString::number(datetimeend) << " datetimebegin " << QString::number(datetimebegin) << endl;
+ if (datetimebegin <= dt1.toTime_t()) datetimebegin = dt1.toTime_t();
+ if (datetimeend >= dt2.toTime_t()) datetimeend = dt2.toTime_t();
+
+
+
+ if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Online))
+ online += datetimeend - datetimebegin;
+ else if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Away))
+ away += datetimeend - datetimebegin;
+ else if (values[i]==Kopete::OnlineStatus::statusTypeToString(Kopete::OnlineStatus::Offline))
+ offline += datetimeend - datetimebegin;
+ }
+
+ if (online > away && online > offline) return i18n("Online");
+ else if (away > online && away > offline) return i18n("Away");
+ else if (offline > online && offline > away) return i18n("Offline");
+
+ return "";
+}
+
+// QDateTime StatisticsContact::nextOfflineEvent()
+// {
+// return nextEvent(Kopete::OnlineStatus::Offline);
+// }
+//
+// QDateTime StatisticsContact::nextOnlineEvent()
+// {
+// return nextEvent(Kopete::OnlineStatus::Online);
+// }
+
+// QDateTime StatisticsContact::nextEvent(const Kopete::OnlineStatus::StatusType& status)
+// {
+//
+// }
+
+QValueList<QTime> StatisticsContact::mainEvents(const Kopete::OnlineStatus::StatusType& status)
+{
+ QStringList buffer;
+ QValueList<QTime> mainEvents;
+
+ if (m_statisticsContactId.isEmpty())
+ return mainEvents;
+
+ QDateTime currentDateTime = QDateTime::currentDateTime();
+ buffer = m_db->query(QString("SELECT datetimebegin, datetimeend, status FROM contactstatus WHERE metacontactid LIKE '%1' ORDER BY datetimebegin").arg(m_statisticsContactId));
+
+
+ // Only select the events for which the previous is not Unknown AND the status is status.
+ QStringList values;
+ for (uint i=0; i<buffer.count(); i += 3)
+ {
+ if (buffer[i+2] == Kopete::OnlineStatus::statusTypeToString(status)
+ && abs(buffer[i+1].toInt()-buffer[i].toInt()) > 120)
+ {
+ values.push_back(buffer[i]);
+ }
+ }
+
+ // No entries for this contact ...
+ if (!values.count()) return mainEvents;
+
+ // First we compute the average number of events/day : avEventsPerDay;
+ int avEventsPerDay = 0;
+ QDateTime dt1, dt2;
+ dt1.setTime_t(values[0].toInt());
+ dt2.setTime_t(values[values.count()-1].toInt());
+
+ avEventsPerDay = qRound((double)values.count()/(double)dt1.daysTo(dt2));
+ kdDebug() << "statistics: average events per day : " <<avEventsPerDay << endl;
+
+ // We want to work on hours
+ QValueList<int> hoursValues;
+ for (uint i=0; i<values.count(); i++)
+ {
+ QDateTime dt;
+ dt.setTime_t(values[i].toInt());
+ hoursValues.push_back(QTime(0, 0, 0).secsTo(dt.time()));
+ }
+
+ // Sort the list
+ qHeapSort(hoursValues);
+
+ // Then we put some centroids (centroids in [0..24[)
+ QValueList<int> centroids;
+ int incr=qRound((double)hoursValues.count()/(double)avEventsPerDay);
+ incr = incr ? incr : 1;
+ for (uint i=0; i<hoursValues.count(); i+=incr)
+ {
+ centroids.push_back(hoursValues[i]);
+ kdDebug() << "statistics: add a centroid : " << centroids[centroids.count()-1] << endl;
+ }
+
+
+ // We need to compute the centroids
+ centroids = computeCentroids(centroids, hoursValues);
+
+ // Convert to QDateTime
+ for (uint i=0; i<centroids.count(); i++)
+ {
+ kdDebug() << "statistics: new centroid : " << centroids[i] << endl;
+
+ QTime dt(0, 0, 0);
+ dt = dt.addSecs(centroids[i]);
+ mainEvents.push_back(dt);
+ }
+
+
+ return mainEvents;
+}
+
+QValueList<int> StatisticsContact::computeCentroids(const QValueList<int>& centroids, const QValueList<int>& values)
+{
+ kdDebug() << "statistics: enter compute centroids"<< endl;
+
+ QValueList<int> whichCentroid; // whichCentroid[i] = j <=> values[i] has centroid j for closest one
+ QValueList<int> newCentroids;
+ for (uint i=0; i<values.count(); i++)
+ // Iterates over the values. For each one we need to get the closest centroid.
+ {
+ int value = values[i];
+ int distanceToNearestCentroid = abs(centroids[0]-value);
+ int nearestCentroid = 0;
+ for (uint j=1; j<centroids.count(); j++)
+ {
+ if (abs(centroids[j]-value) < distanceToNearestCentroid)
+ {
+ distanceToNearestCentroid = abs(centroids[j]-value);
+ nearestCentroid = j;
+ }
+ }
+ whichCentroid.push_back(nearestCentroid);
+ }
+
+ // Recompute centroids
+ newCentroids = centroids;
+
+ for (uint i=0; i<newCentroids.count(); i++)
+ {
+ kdDebug() << "statistics: compute new centroids"<< i << endl;
+ int weight = 0;
+ for (uint j=0; j<values.count(); j++)
+ {
+ int value = values[j];
+ if (whichCentroid[j] == i)
+ {
+ newCentroids[i] = qRound((double)(value + newCentroids[i]*weight)/(double)(weight + 1));
+ weight++;
+
+ }
+ }
+ }
+
+
+
+ // Should we recompute or are we OK ?
+ int dist = 0;
+ for (uint i=0; i < newCentroids.count(); i++)
+ dist += abs(newCentroids[i]-centroids[i]);
+
+ if (dist > 10)
+ return computeCentroids(newCentroids, values);
+ else
+ {
+
+ return newCentroids;
+ }
+}