/*
* Gamers Own Instant Messenger
* Copyright (C) 2005-2006 Herbert Poul (kahless@sphene.net)
* http://goim.sphene.net
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package net.sphene.goim.stats;
import galena.Kernel;
import galena.addins.modules.database.DB;
import galena.addins.modules.database.DB.DBConnection;
import galena.message.Message;
import galena.message.StandAloneMessageHandler;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sphene.galena.addins.AddinImplementationDefinition;
import net.sphene.galena.addins.AddinInterfaceDefinition;
import net.sphene.galena.addins.AddinInterfaceMethodDefinition;
import net.sphene.galena.addins.GalenaArgsInterface;
import net.sphene.galena.addins.GalenaViewAddin;
import net.sphene.goim.stats.game.GameStatusExtension;
import org.jivesoftware.smack.AccountManager;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
@AddinInterfaceDefinition(
defaultcommandprefix = "jabberstatscollector"
)
@AddinImplementationDefinition(
author = "Herbert Poul",
requirements = { "galena.addins.modules.database.PgSQL" }
)
public class StatsCollectorBot extends GalenaViewAddin {
private DB db;
private String jid = "stats@localhost";
private String password = "";
private String host = null;
private String resource = "StatsCollectorBot";
private int port = 5222;
protected XMPPConnection connection;
protected Map<String, PresenceWrapper> presences;
public DB getDB() { return db; }
public static class PresenceWrapper {
Date date;
Presence presence;
public PresenceWrapper(Date date, Presence presence) {
this.date = date; this.presence = presence;
}
}
@Override
public void init(Object[] requirements) {
this.db = (DB)requirements[0];
doConnect();
}
@Override
public void registerHandler() {
super.registerHandler();
kernel.registerMessageHandler(new StandAloneMessageHandler(null) {
@Override
protected int handleMessage(Message msg) {
// TODO Auto-generated method stub
return 0;
} }.setMessagetype(Message.MSG_FINALIZE));
}
public void doConnect() {
new Thread("StatsCollectorBot") {
public void run() {
while(!connect()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}.start();
}
public boolean connect() {
try {
//XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(host,port,StringUtils.parseServer(jid));
presences = new HashMap<String,PresenceWrapper>();
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
System.out.println("Received packet: " + packet.toXML());
if(!(packet instanceof Presence)) return;
//System.out.println(" -- IS Presence packet");
Presence presence = (Presence)packet;
if(presence.getType() == Presence.Type.SUBSCRIBE) {
Presence response = new Presence(Presence.Type.SUBSCRIBED);
response.setTo(presence.getFrom());
connection.sendPacket(response);
Presence req = new Presence(Presence.Type.SUBSCRIBE);
req.setTo(presence.getFrom());
connection.sendPacket(req);
} else if(presence.getType() == Presence.Type.AVAILABLE) {
PresenceWrapper oldWrapper = presences.put(presence.getFrom(),new PresenceWrapper(new Date(),presence));
if(oldWrapper != null)
logPresence(oldWrapper);
} else if(presence.getType() == Presence.Type.UNAVAILABLE) {
PresenceWrapper oldWrapper = presences.remove(presence.getFrom());
if(oldWrapper != null)
logPresence(oldWrapper);
}
} }, new AndFilter());//new PacketTypeFilter(Presence.class));
connection.addConnectionListener(new ConnectionListener(){
public void connectionClosed() {
doConnect();
}
public void connectionClosedOnError(Exception e) {
e.printStackTrace();
}});
login();
return true;
} catch (XMPPException e) {
e.printStackTrace();
if(e.getXMPPError() != null && e.getXMPPError().getCode() == 401) {
Kernel.debug(this,"Unauthorized.. trying to register ..",-1);
AccountManager manager = new AccountManager(connection);
try {
manager.createAccount(StringUtils.parseName(jid),password);
login();
return true;
} catch (XMPPException e1) {
e1.printStackTrace();
return false;
}
} else {
return false;
}
}
}
protected void logPresence(PresenceWrapper oldWrapper) {
DBConnection db = this.db.getDBConnection(false);
int id = db.sendInsert(null,"goim_presence_log","jid,resource,mode,start,\"end\"",
StringUtils.parseBareAddress(oldWrapper.presence.getFrom()).toLowerCase(),
StringUtils.parseResource(oldWrapper.presence.getFrom()),
oldWrapper.presence.getMode().toString(),
oldWrapper.date,
new Date());
db.commit();
Iterator i = oldWrapper.presence.getExtensions();
while(i.hasNext()) {
PacketExtension ex = (PacketExtension)i.next();
if(ex instanceof GameStatusExtension) {
GameStatusExtension status = (GameStatusExtension)ex;
String gameId = status.gameId;
String destination;
if(status.gameId.equals("lfss2")) {
destination = status.atts.get("servername");
} else {
destination = GameStatusExtension.formatSocketAddress(status.target);
}
db.sendInsert(null,"goim_server","game,destination,presence",gameId,destination,id);
db.commit();
}
}
db.close();
}
public void login() throws XMPPException {
connection.login(StringUtils.parseName(jid),password,resource);
connection.getRoster().setSubscriptionMode(Roster.SUBSCRIPTION_MANUAL);
System.out.println("Logged In.");
}
@Override
public void beforeInit(Message msg) {
super.beforeInit(msg);
jid = msg.getOption("jid",jid);
password = msg.getOption("password",password);
host = msg.getOption("host",StringUtils.parseServer(jid));
port = Integer.parseInt(msg.getOption("port",Integer.toString(port)));
resource = msg.getOption("resource",resource);
}
@AddinInterfaceMethodDefinition(
userlevel = 0
)
public String test() {
return "Just testing...";
}
@AddinInterfaceMethodDefinition(
userlevel = 9
)
public void logAllPresences(GalenaArgsInterface args) {
synchronized (presences) {
for( String jid : presences.keySet() ) {
logPresence(presences.get(jid));
presences.remove(jid);
}
}
}
public Presence getPresenceOf(String user) {
return connection.getRoster().getPresence(user);
}
public Roster getRoster() { return connection.getRoster(); }
/**
* Aggregates the statistics for all users in the roster.
* @param args
* @param jid
*/
@AddinInterfaceMethodDefinition(
userlevel = 9
)
public void aggregateAllStatisticsOfRoster(GalenaArgsInterface args) {
Iterator i = getRoster().getEntries();
while(i.hasNext()) {
RosterEntry entry = (RosterEntry)i.next();
System.out.println("Aggregating Statistics for user: " + entry.getUser());
aggregateStatistics(args,entry.getUser());
}
}
/**
* Aggregates the statistics of a single user.
* @param args
* @param jid
*/
@AddinInterfaceMethodDefinition(
userlevel = 9
)
public void aggregateStatistics(GalenaArgsInterface args, String jid) {
jid = jid.toLowerCase();
DBConnection db = this.db.getDBConnection(true);
// First check if user has an entry in the JID table.
int jidID;
{
List<Map<String,Object>> jidentry = db.getListOfMaps("SELECT * FROM goim_stats_jid WHERE jid = ?",jid);
if(jidentry != null && jidentry.size() > 0)
jidID = (Integer)jidentry.get(0).get("id");
else
jidID = db.sendInsert(args.getUser(),"goim_stats_jid","jid",jid);
}
boolean dontlogthisday = false;
// Now check when the last day entry was made.
List<Map<String,Object>> listOfPresenceLog;
{
List<Map<String,Object>> lastDayEntry = db.getListOfMaps("SELECT * FROM goim_stats_day WHERE jid = ? ORDER BY day DESC LIMIT 1",jidID);
if(lastDayEntry != null && lastDayEntry.size() > 0) {
Date lastDay = (Date)lastDayEntry.get(0).get("day");
Integer id = (Integer)lastDayEntry.get(0).get("id");
Calendar cal = Calendar.getInstance();
cal.setTime(lastDay);
// We recalculate the last day ...
db.sendDelete(args.getUser(),"goim_stats_game","day = ?",id);
db.sendDelete(args.getUser(),"goim_stats_presencestatus","day = ?",id);
db.sendDelete(args.getUser(),"goim_stats_day","id = ?",id);
// To recalculate the last day we also need to recalculate
// the day before so we get presences which started there and
// lasted until this day.
cal.add(Calendar.DAY_OF_MONTH,-1);
listOfPresenceLog = db.getListOfMaps("SELECT * FROM goim_presence_log WHERE jid = ? AND start > ? ORDER BY start",jid,cal.getTime());
dontlogthisday = true;
} else {
// No entry yet
listOfPresenceLog = db.getListOfMaps("SELECT * FROM goim_presence_log WHERE jid = ? ORDER BY start",jid);
}
}
Calendar currday = null; // The start of the current day.
Calendar nextday = null; // The start of the next day (ie. the end of the current)
HashMap<String,Long> gamesPlayed = new HashMap<String,Long>();
HashMap<String,Long> presenceModes = new HashMap<String,Long>();
HashMap<String,Long> nextGamesPlayed = new HashMap<String,Long>();
HashMap<String,Long> nextPresenceModes = new HashMap<String,Long>();
/// OK .. really BAD algorithm .. but it works ... somehow .. i guess/hope ?!
for(Map<String,Object> logEntry : listOfPresenceLog) {
Date start = (Date)logEntry.get("start");
Date end = (Date)logEntry.get("end");
String mode = (String)logEntry.get("mode");
if(nextday == null) {
nextday = Calendar.getInstance();
currday = Calendar.getInstance();
nextday.clear();
Calendar startcal = Calendar.getInstance();
startcal.setTime(start);
nextday.set(Calendar.YEAR,startcal.get(Calendar.YEAR));
nextday.set(Calendar.MONTH,startcal.get(Calendar.MONTH));
nextday.set(Calendar.DAY_OF_MONTH,startcal.get(Calendar.DAY_OF_MONTH));
currday = (Calendar)nextday.clone();
nextday.add(Calendar.DAY_OF_MONTH,1);
} else {
if(start.after(nextday.getTime())) {
System.out.println("Day: " + nextday.getTime().toString() + " .... " + start.toString());
if(!dontlogthisday)
logDay(args, jidID, currday, gamesPlayed, presenceModes);
dontlogthisday = false;
gamesPlayed = nextGamesPlayed;
presenceModes = nextPresenceModes;
nextGamesPlayed = new HashMap<String,Long>();
nextPresenceModes = new HashMap<String,Long>();
currday = (Calendar)nextday.clone();
nextday.add(Calendar.DAY_OF_MONTH,1);
}
}
if(start.after(nextday.getTime())) continue;
long durationForNextDay = 0;
long durationInMilliSeconds = end.getTime() - start.getTime();
if(end.after(nextday.getTime())) {
long tmpdur = nextday.getTime().getTime() - start.getTime();
durationForNextDay = (durationInMilliSeconds - tmpdur);
durationInMilliSeconds = tmpdur;
}
{
Long d = presenceModes.get(mode);
if(d == null) d = durationInMilliSeconds;
else d = d + durationInMilliSeconds;
presenceModes.put(mode,d);
if(durationForNextDay > 0) {
d = nextPresenceModes.get(mode);
if(d == null) d = durationForNextDay;
else d = d + durationForNextDay;
nextPresenceModes.put(mode,d);
}
}
List<Map<String,Object>> games = db.getListOfMaps("SELECT * FROM goim_server WHERE presence = ?",logEntry.get("id"));
for(Map<String,Object> game : games) {
String gameName = (String)game.get("game");
Long d = gamesPlayed.get(gameName);
if(d == null) d = durationInMilliSeconds;
else d = d + durationInMilliSeconds;
gamesPlayed.put(gameName,d);
if(durationForNextDay > 0) {
d = nextGamesPlayed.get(gameName);
if(d == null) d = durationForNextDay;
else d = d + durationForNextDay;
nextGamesPlayed.put(gameName,d);
}
}
}
if(currday != null && !dontlogthisday)
logDay(args, jidID, currday, gamesPlayed, presenceModes);
db.close();
}
private void logDay(GalenaArgsInterface args, int jidID, Calendar currday, HashMap<String, Long> gamesPlayed, HashMap<String, Long> presenceModes) {
// Log day ...
int dayID = db.sendInsert(args.getUser(),"goim_stats_day","day,jid",/*start*/currday.getTime(),jidID);
for(String presence : presenceModes.keySet()) {
long durationMilli = presenceModes.get(presence);
int durationMinute = (int)(durationMilli / 1000 / 60);
db.sendInsert(args.getUser(),"goim_stats_presencestatus","day,duration,mode",dayID,durationMinute,presence);
System.out.println(" Presence: " + presence + " duration: " + durationMinute);
}
for(String game : gamesPlayed.keySet()) {
long durationMilli = gamesPlayed.get(game);
int durationMinute = (int)(durationMilli / 1000 / 60);
db.sendInsert(args.getUser(),"goim_stats_game","day,duration,game",dayID,durationMinute,game);
}
}
}