/**
*
*/
package icy.system.audit;
import icy.file.FileUtil;
import icy.file.xml.XMLPersistent;
import icy.file.xml.XMLPersistentHelper;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginDescriptor.PluginIdent;
import icy.plugin.PluginLoader;
import icy.plugin.abstract_.Plugin;
import icy.system.IcyExceptionHandler;
import icy.util.DateUtil;
import icy.util.XMLUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.w3c.dom.Node;
/**
* @author Stephane
*/
public class AuditStorage implements XMLPersistent
{
private static final String ID_PLUGIN = "plugin";
private static final String AUDIT_FILENAME = "icy_usage.xml";
private static final long SAVE_INTERVAL = 1000 * 60;
private final Map<PluginIdent, PluginStorage> pluginStats;
private long lastSaveTime;
public AuditStorage()
{
super();
pluginStats = new HashMap<PluginIdent, PluginStorage>();
try
{
// load usage data from XML file
XMLPersistentHelper.loadFromXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
// clean obsoletes data
clean();
}
catch (Exception e)
{
System.out.println("Warning: can't reload usage statistics data.");
IcyExceptionHandler.showErrorMessage(e, false, false);
}
lastSaveTime = System.currentTimeMillis();
}
public void save()
{
try
{
// save XML data
XMLPersistentHelper.saveToXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
}
catch (Exception e)
{
System.out.println("Warning: can't save usage statistics data.");
IcyExceptionHandler.showErrorMessage(e, false, false);
}
}
private void clean()
{
final List<PluginIdent> empties = new ArrayList<PluginIdent>();
synchronized (pluginStats)
{
// clean statistics
for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
{
entry.getValue().clean();
if (entry.getValue().isEmpty())
empties.add(entry.getKey());
}
// remove empty ones
for (PluginIdent ident : empties)
pluginStats.remove(ident);
}
}
private void autoSave()
{
final long currentTime = System.currentTimeMillis();
// interval elapsed
if ((currentTime - lastSaveTime) > SAVE_INTERVAL)
{
// save statistics to disk
save();
lastSaveTime = currentTime;
}
}
private PluginStorage getStorage(PluginIdent ident, boolean autoCreate)
{
PluginStorage result;
synchronized (pluginStats)
{
result = pluginStats.get(ident);
if ((result == null) && autoCreate)
{
result = new PluginStorage();
pluginStats.put(ident, result);
}
}
return result;
}
public void pluginLaunched(Plugin plugin)
{
final PluginDescriptor descriptor;
if (plugin.isBundled())
descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
else
descriptor = plugin.getDescriptor();
// ignore if no descriptor
if (descriptor == null)
return;
// ignore if missing version info
if (descriptor.getVersion().isEmpty())
return;
// ignore kernel plugins
if (descriptor.isKernelPlugin())
return;
// increment launch
getStorage(descriptor.getIdent(), true).incLaunch(DateUtil.keepDay(System.currentTimeMillis()));
// save to disk if needed
autoSave();
}
public void pluginInstancied(Plugin plugin)
{
final PluginDescriptor descriptor;
if (plugin.isBundled())
descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
else
descriptor = plugin.getDescriptor();
// ignore if no descriptor
if (descriptor == null)
return;
// ignore if missing version info
if (descriptor.getVersion().isEmpty())
return;
// ignore kernel plugins
if (descriptor.isKernelPlugin())
return;
// increment instance
getStorage(descriptor.getIdent(), true).incInstance(DateUtil.keepDay(System.currentTimeMillis()));
// save to disk if needed
autoSave();
}
/**
* Upload statistics to website
*/
public boolean upload(int id)
{
final List<PluginIdent> dones = new ArrayList<PluginIdent>();
final List<Entry<PluginIdent, PluginStorage>> entries;
synchronized (pluginStats)
{
entries = new ArrayList<Entry<PluginIdent, PluginStorage>>(pluginStats.entrySet());
}
try
{
for (Entry<PluginIdent, PluginStorage> entry : entries)
{
if (entry.getValue().upload(id, entry.getKey()))
dones.add(entry.getKey());
// interrupt upload
if (Thread.interrupted())
break;
}
}
finally
{
// remove stats which has been correctly uploaded
synchronized (pluginStats)
{
for (PluginIdent ident : dones)
pluginStats.remove(ident);
}
}
return pluginStats.isEmpty();
}
@Override
public boolean loadFromXML(Node node)
{
if (node == null)
return false;
synchronized (pluginStats)
{
pluginStats.clear();
for (Node n : XMLUtil.getChildren(node, ID_PLUGIN))
{
final PluginIdent ident = new PluginIdent();
final PluginStorage storage = new PluginStorage();
ident.loadFromXMLShort(n);
storage.loadFromXML(n);
pluginStats.put(ident, storage);
}
}
return true;
}
@Override
public boolean saveToXML(Node node)
{
if (node == null)
return false;
XMLUtil.removeAllChildren(node);
synchronized (pluginStats)
{
for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
{
final Node n = XMLUtil.addElement(node, ID_PLUGIN);
entry.getKey().saveToXMLShort(n);
entry.getValue().saveToXML(n);
}
}
return true;
}
private class PluginStorage implements XMLPersistent
{
private static final String ID_CLASSNAME = PluginIdent.ID_CLASSNAME;
private static final String ID_VERSION = PluginIdent.ID_VERSION;
private static final String ID_LAUNCH = "launch";
private static final String ID_INSTANCE = "instance";
private static final String ID_STATS_LAUNCH = "stats_" + ID_LAUNCH;
private static final String ID_STATS_INSTANCE = "stats_" + ID_INSTANCE;
private static final String ID_DATE = "date";
private static final String ID_VALUE = "value";
private static final long DAY_TO_KEEP = 30L;
private final Map<Long, Long> launchStats;
private final Map<Long, Long> instanceStats;
public PluginStorage()
{
super();
launchStats = new HashMap<Long, Long>();
instanceStats = new HashMap<Long, Long>();
}
public void clean()
{
List<Long> olds = new ArrayList<Long>();
final long dayInterval = 1000 * 60 * 60 * 24;
final long timeLimit = System.currentTimeMillis() - (DAY_TO_KEEP * dayInterval);
// find obsoletes entries
olds.clear();
for (Long date : launchStats.keySet())
if (date.longValue() < timeLimit)
olds.add(date);
// remove them
for (Long date : olds)
launchStats.remove(date);
// find obsoletes entries
olds.clear();
for (Long date : instanceStats.keySet())
if (date.longValue() < timeLimit)
olds.add(date);
// remove them
for (Long date : olds)
instanceStats.remove(date);
}
public boolean isEmpty()
{
return launchStats.isEmpty() && instanceStats.isEmpty();
}
public long getLaunch(Long date)
{
final Long result = launchStats.get(date);
if (result == null)
return 0L;
return result.longValue();
}
public long getInstance(Long date)
{
final Long result = instanceStats.get(date);
if (result == null)
return 0L;
return result.longValue();
}
public void incLaunch(long date)
{
final Long key = Long.valueOf(date);
launchStats.put(key, Long.valueOf(getLaunch(key) + 1L));
}
public void incInstance(long date)
{
final Long key = Long.valueOf(date);
instanceStats.put(key, Long.valueOf(getInstance(key) + 1L));
}
private Map<String, String> getIdParam(int id)
{
// id ok ?
if (id != -1)
{
final Map<String, String> values = new HashMap<String, String>();
// set id
values.put(Audit.ID_ICY_ID, Integer.toString(id));
return values;
}
return null;
}
public boolean upload(int id, PluginIdent ident)
{
// init params
final Map<String, String> params = getIdParam(id);
int offset;
// set plugin identity
params.put(ID_CLASSNAME, ident.getClassName());
params.put(ID_VERSION, ident.getVersion().toString());
offset = 0;
// build params for launch statistic
for (Entry<Long, Long> entry : launchStats.entrySet())
{
// set date
params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
// set value
params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
offset++;
}
offset = 0;
// build params for instance statistic
for (Entry<Long, Long> entry : instanceStats.entrySet())
{
// set date
params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
// set value
params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
offset++;
}
try
{
// null return means website did not accepted them...
if (NetworkUtil.postData(Audit.URL_AUDIT_PLUGIN, params) == null)
return false;
}
catch (IOException e)
{
return false;
}
// clear stats just to be sure to not send them twice
launchStats.clear();
instanceStats.clear();
return true;
}
@Override
public boolean loadFromXML(Node node)
{
if (node == null)
return false;
launchStats.clear();
for (Node n : XMLUtil.getChildren(node, ID_LAUNCH))
{
final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);
launchStats.put(Long.valueOf(date), Long.valueOf(value));
}
instanceStats.clear();
for (Node n : XMLUtil.getChildren(node, ID_INSTANCE))
{
final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);
instanceStats.put(Long.valueOf(date), Long.valueOf(value));
}
return true;
}
@Override
public boolean saveToXML(Node node)
{
if (node == null)
return false;
for (Entry<Long, Long> entry : launchStats.entrySet())
{
final Node n = XMLUtil.addElement(node, ID_LAUNCH);
XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
}
for (Entry<Long, Long> entry : instanceStats.entrySet())
{
final Node n = XMLUtil.addElement(node, ID_INSTANCE);
XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
}
return true;
}
}
}