You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
17 KiB
516 lines
17 KiB
/*
|
|
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 <tqvaluelist.h>
|
|
#include <tquuid.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 = TQDateTime::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",TQString::number(m_timeBetweenTwoMessages),
|
|
TQString::number(m_timeBetweenTwoMessagesOn), m_timeBetweenTwoMessagesChanged);
|
|
commonStatsSave("messagelength",TQString::number(m_messageLength), TQString::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
|
|
TQStringList buffer = m_db->query(TQString("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(TQString("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 = TQUuid::createUuid().toString();
|
|
}
|
|
|
|
// Assign contactId to m_statisticsContactId
|
|
m_db->query(TQString("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
|
|
TQString lastTalk;
|
|
TQString dummy = "";
|
|
commonStatsCheck("lasttalk", lastTalk, dummy);
|
|
if (lastTalk.isEmpty())
|
|
{
|
|
m_lastTalk.setTime_t(0);
|
|
m_lastTalkChanged = true;
|
|
}
|
|
else
|
|
m_lastTalk = TQDateTime::fromString(lastTalk);
|
|
|
|
|
|
// Get last time a message was received
|
|
m_lastMessageReceived = TQDateTime::currentDateTime();
|
|
|
|
|
|
// Check for lastPresent
|
|
TQString lastPresent = "";
|
|
commonStatsCheck("lastpresent", lastPresent, dummy);
|
|
if (lastPresent.isEmpty())
|
|
{
|
|
m_lastPresent.setTime_t(0);
|
|
m_lastPresentChanged = true;
|
|
}
|
|
else
|
|
m_lastPresent = TQDateTime::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(TQString("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(TQString("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(TQString("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(TQString("DELETE FROM contacts WHERE statisticid LIKE '%1';").arg(m_statisticsContactId));
|
|
m_db->query(TQString("DELETE FROM contactstatus WHERE metacontactid LIKE '%1';").arg(m_statisticsContactId));
|
|
m_db->query(TQString("DELETE FROM commonstats WHERE metacontactid LIKE '%1';").arg(m_statisticsContactId));
|
|
|
|
m_statisticsContactId = TQString();
|
|
}
|
|
|
|
void StatisticsContact::commonStatsSave(const TQString name, const TQString statVar1, const TQString statVar2, const bool statVarChanged)
|
|
{
|
|
// Only update the database if there was a change
|
|
if (!statVarChanged) return;
|
|
|
|
if (m_statisticsContactId.isEmpty())
|
|
return;
|
|
|
|
m_db->query(TQString("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 TQString name, int& statVar1, int& statVar2, const int defaultValue1, const int defaultValue2)
|
|
{
|
|
TQString a = TQString::number(statVar1);
|
|
TQString b = TQString::number(statVar2);
|
|
|
|
commonStatsCheck(name, a, b, TQString::number(defaultValue1), TQString::number(defaultValue2));
|
|
|
|
statVar1 = a.toInt();
|
|
statVar2 = b.toInt();
|
|
}
|
|
|
|
void StatisticsContact::commonStatsCheck(const TQString name, TQString& statVar1, TQString& statVar2, const TQString defaultValue1, const TQString defaultValue2)
|
|
{
|
|
if (m_statisticsContactId.isEmpty())
|
|
return;
|
|
|
|
TQStringList buffer = m_db->query(TQString("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(TQString("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 length
|
|
*
|
|
* <li>Change last-talk datetime
|
|
* </ul>
|
|
*
|
|
*/
|
|
void StatisticsContact::newMessageReceived(Kopete::Message& m)
|
|
{
|
|
kdDebug() << "statistics: new message received" << endl;
|
|
TQDateTime currentDateTime = TQDateTime::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 length
|
|
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;
|
|
|
|
TQDateTime currentDateTime = TQDateTime::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() << " : "<< TQString::number(m_oldStatus) << endl;
|
|
m_db->query(TQString("INSERT INTO contactstatus "
|
|
"(metacontactid, status, datetimebegin, datetimeend) "
|
|
"VALUES('%1', '%2', '%3', '%4'" ");").arg(m_statisticsContactId).arg(Kopete::OnlineStatus::statusTypeToString(m_oldStatus)).arg(TQString::number(m_oldStatusDateTime.toTime_t())).arg(TQString::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(TQDateTime dt, Kopete::OnlineStatus::StatusType status)
|
|
{
|
|
if (m_statisticsContactId.isEmpty())
|
|
return false;
|
|
|
|
TQStringList values = m_db->query(TQString("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;
|
|
}
|
|
|
|
TQString StatisticsContact::statusAt(TQDateTime dt)
|
|
{
|
|
if (m_statisticsContactId.isEmpty())
|
|
return "";
|
|
|
|
TQStringList values = m_db->query(TQString("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 "";
|
|
}
|
|
|
|
TQString StatisticsContact::mainStatusDate(const TQDate& date)
|
|
{
|
|
if (m_statisticsContactId.isEmpty())
|
|
return "";
|
|
|
|
TQDateTime dt1(date, TQTime(0,0,0));
|
|
TQDateTime dt2(date.addDays(1), TQTime(0,0,0));
|
|
kdDebug() << "dt1:" << dt1.toString() << " dt2:" << dt2.toString() << endl;
|
|
TQString request = TQString("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;
|
|
TQStringList 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 " << TQString::number(datetimeend) << " datetimebegin " << TQString::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 "";
|
|
}
|
|
|
|
// TQDateTime StatisticsContact::nextOfflineEvent()
|
|
// {
|
|
// return nextEvent(Kopete::OnlineStatus::Offline);
|
|
// }
|
|
//
|
|
// TQDateTime StatisticsContact::nextOnlineEvent()
|
|
// {
|
|
// return nextEvent(Kopete::OnlineStatus::Online);
|
|
// }
|
|
|
|
// TQDateTime StatisticsContact::nextEvent(const Kopete::OnlineStatus::StatusType& status)
|
|
// {
|
|
//
|
|
// }
|
|
|
|
TQValueList<TQTime> StatisticsContact::mainEvents(const Kopete::OnlineStatus::StatusType& status)
|
|
{
|
|
TQStringList buffer;
|
|
TQValueList<TQTime> mainEvents;
|
|
|
|
if (m_statisticsContactId.isEmpty())
|
|
return mainEvents;
|
|
|
|
TQDateTime currentDateTime = TQDateTime::currentDateTime();
|
|
buffer = m_db->query(TQString("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.
|
|
TQStringList 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;
|
|
TQDateTime dt1, dt2;
|
|
dt1.setTime_t(values[0].toInt());
|
|
dt2.setTime_t(values[values.count()-1].toInt());
|
|
|
|
avEventsPerDay = tqRound((double)values.count()/(double)dt1.daysTo(dt2));
|
|
kdDebug() << "statistics: average events per day : " <<avEventsPerDay << endl;
|
|
|
|
// We want to work on hours
|
|
TQValueList<int> hoursValues;
|
|
for (uint i=0; i<values.count(); i++)
|
|
{
|
|
TQDateTime dt;
|
|
dt.setTime_t(values[i].toInt());
|
|
hoursValues.push_back(TQTime(0, 0, 0).secsTo(dt.time()));
|
|
}
|
|
|
|
// Sort the list
|
|
qHeapSort(hoursValues);
|
|
|
|
// Then we put some centroids (centroids in [0..24[)
|
|
TQValueList<int> centroids;
|
|
int incr=tqRound((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 TQDateTime
|
|
for (uint i=0; i<centroids.count(); i++)
|
|
{
|
|
kdDebug() << "statistics: new centroid : " << centroids[i] << endl;
|
|
|
|
TQTime dt(0, 0, 0);
|
|
dt = dt.addSecs(centroids[i]);
|
|
mainEvents.push_back(dt);
|
|
}
|
|
|
|
|
|
return mainEvents;
|
|
}
|
|
|
|
TQValueList<int> StatisticsContact::computeCentroids(const TQValueList<int>& centroids, const TQValueList<int>& values)
|
|
{
|
|
kdDebug() << "statistics: enter compute centroids"<< endl;
|
|
|
|
TQValueList<int> whichCentroid; // whichCentroid[i] = j <=> values[i] has centroid j for closest one
|
|
TQValueList<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] = tqRound((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;
|
|
}
|
|
}
|