package esmska.update; import esmska.data.Config; import esmska.data.Contacts; import esmska.data.History; import esmska.data.History.Record; import esmska.data.Links; import esmska.data.Signature; import esmska.data.Signatures; import esmska.utils.RuntimeUtils; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.text.DateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.TreeSet; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.time.DateUtils; /** Class handling everything needed about collecting and posting program usage statistics. */ public class Statistics { private static final Logger logger = Logger.getLogger(Statistics.class.getName()); private static final Config config = Config.getInstance(); /** If the UUID hasn't already been changed this month, changes it. Otherwise * does nothing. */ public static void refreshUUID() { int currentMonth = Calendar.getInstance().get(Calendar.MONTH); boolean needRegen = StringUtils.isEmpty(config.getUUID()) || config.getUUIDMonth() != currentMonth; if (needRegen) { logger.fine("Regenerating a new UUID"); config.setUUID(UUID.randomUUID().toString()); config.setUUIDMonth(currentMonth); } } /** Collect program usage info and return it as a JSON object. */ public static JSONObject collectUsageInfo() { JSONObject info = new JSONObject(); // program info info.put("uuid", config.getUUID()); info.put("version", Config.getLatestVersion()); info.put("stable", Config.isStableVersion()); // system info info.put("os", WordUtils.capitalizeFully(RuntimeUtils.detectOS().name())); String desktop = null; if (RuntimeUtils.isGnomeDesktop()) { desktop = "Gnome"; } else if (RuntimeUtils.isKDEDesktop()) { desktop = "KDE"; } else { desktop = "other"; } info.put("desktop", desktop); String java = null; if (RuntimeUtils.isOracleJava()) { java = "Oracle"; } else if (RuntimeUtils.isOpenJDK()) { java = "OpenJDK"; } else if (RuntimeUtils.isAppleJava()) { java = "Apple"; } else { java = "other"; } info.put("java", java); info.put("language", Locale.getDefault().getLanguage()); // user configuration info.put("lookAndFeel", WordUtils.capitalizeFully(config.getLookAndFeel().name())); info.put("countryPrefix", config.getCountryPrefix()); info.put("useProxy", config.isUseProxy()); info.put("notificationIcon", config.isNotificationIconVisible()); info.put("advancedSettings", config.isShowAdvancedSettings()); info.put("customSignatures", Signatures.getInstance().getAll().size()); Signature defaultSig = Signatures.getInstance().get(Signature.DEFAULT.getProfileName()); info.put("defaultSenderNumber", StringUtils.isNotEmpty(defaultSig.getUserNumber())); info.put("defaultSenderName", StringUtils.isNotEmpty(defaultSig.getUserName())); // user data info.put("contacts", Contacts.getInstance().size()); info.put("history", History.getInstance().getRecords().size()); info.put("usedGateways", JSONArray.fromObject(getUsedGateways())); return info; } /** Get set of gateway names that were successfully used in the last * three months. */ private static TreeSet<String> getUsedGateways() { List<Record> records = History.getInstance().getRecords(); // take only last 1000 history records records = records.subList(Math.max(0, records.size()-1000), records.size()); // take only history records in the last 90 days Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_YEAR, -90); Date historyLimit = cal.getTime(); TreeSet<String> gateways = new TreeSet<String>(); for (Record record : records) { if (record.getDate().before(historyLimit)) { continue; } gateways.add(record.getGateway()); } return gateways; } /** Send program usage info to Esmska server if appropriate * (check with {@link #shouldSend()}) */ public static void sendUsageInfo() { if (!shouldSend()) { logger.fine("Not sending usage info"); return; } logger.fine("Sending usage info"); JSONObject info = collectUsageInfo(); final String data = info.toString(2); try { Thread t = new Thread(new Runnable() { @Override public void run() { try { URL url = new URL(Links.SEND_STATS); URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setUseCaches(false); OutputStream out = conn.getOutputStream(); out.write(data.getBytes("UTF-8")); out.flush(); InputStream in = conn.getInputStream(); in.close(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { config.setLastStatsSent(new Date()); } }); logger.finer("Usage info sent"); } catch (Exception ex) { logger.log(Level.WARNING, "Could not send usage info", ex); } } }); t.setDaemon(true); t.start(); } catch (Exception ex) { logger.log(Level.WARNING, "Could not execute sending usage info", ex); } } /** Decide whether to send statistics to Esmska server or not. We don't want to * send stats too often so that the server is not overloaded. The current decision * algorithm: * <ul> * <li>If this is the first time Esmska was ever started, don't send anything.</li> * <li>If this is the first time Esmska was started in current month, always send.</li> * <li>If today is 20th or earlier day in the current month, send stats at maximum * once per 5 days (check last submission date and decide).</li> * <li>If today is 21th or later day in the current month, send stats at maximum * once per 3 days (check last submission date and decide).</li> * </ul> * @return whether it is appropriate to send stats now or not */ public static boolean shouldSend() { // first run if (config.isFirstRun()) { logger.fine("Shouldn't send usage info, first program run"); return false; } // no stored date if (config.getLastStatsSent() == null) { logger.fine("Last usage info submission date unknown, should send usage info"); return true; } Calendar last = DateUtils.toCalendar(config.getLastStatsSent()); Calendar now = Calendar.getInstance(); // compute time difference long diff = now.getTimeInMillis() - last.getTimeInMillis(); double diffDays = diff / (24 * 60 * 60 * 1000.0); DateFormat df = DateFormat.getDateTimeInstance(); logger.log(Level.FINE, "Last usage info sent on {0} ({1,number} days ago)", new Object[]{df.format(last.getTime()), diffDays}); // first run in current month if (last.get(Calendar.MONTH) != now.get(Calendar.MONTH) || last.get(Calendar.YEAR) != now.get(Calendar.YEAR)) { logger.fine("Should send usage info, first program run in the current month"); return true; } int limit = 0; // today is 20th or earlier day if (now.get(Calendar.DAY_OF_MONTH) <= 20) { limit = 5; } else { // today is 21st or later day limit = 3; } if (diffDays >= limit) { logger.fine("Should send usage info"); return true; } else { logger.fine("Shouldn't send usage info"); return false; } } }