* added Google Analytics tracking for usage statistics (application startups, number of downloaded subtitles / episode lists)

This commit is contained in:
Reinhard Pointner 2011-09-21 13:29:21 +00:00
parent a1591a9a1c
commit 1f9ea0f3a1
15 changed files with 293 additions and 18 deletions

BIN
lib/jgat-custom.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,209 @@
package net.sourceforge.filebot;
import static com.dmurph.tracking.JGoogleAnalyticsTracker.GoogleAnalyticsVersion.*;
import static net.sourceforge.filebot.Settings.*;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.util.Map;
import java.util.logging.Logger;
import com.dmurph.tracking.AnalyticsConfigData;
import com.dmurph.tracking.JGoogleAnalyticsTracker;
import com.dmurph.tracking.VisitorData;
import com.sun.jna.Platform;
public class Analytics {
private static final Map<String, String> persistentData = Settings.forPackage(Analytics.class).node("analytics").asMap();
private static final String VISITOR_ID = "visitorId";
private static final String TIMESTAMP_FIRST = "timestampFirst";
private static final String TIMESTAMP_LAST = "timestampLast";
private static final String VISITS = "visits";
private static final VisitorData visitorData = restoreVisitorData();
private static final JGoogleAnalyticsTracker tracker = new JGoogleAnalyticsTracker(getConfig(getApplicationProperty("analytics.WebPropertyID"), visitorData), V_4_7_2);
public static void trackView(Class<?> view, String title) {
trackView(view.getName().replace(',', '/'), title);
}
public static synchronized void trackView(String view, String title) {
if (!tracker.isEnabled())
return;
tracker.trackPageViewFromSearch(view, title, "filebot.sourceforge.net", getJavaVersionIdentifier(), getDeploymentMethod());
}
public static void trackEvent(String category, String action, String label) {
trackEvent(category, action, label, null);
}
public static synchronized void trackEvent(String category, String action, String label, Integer value) {
if (!tracker.isEnabled())
return;
tracker.trackEvent(category, action, label, value);
}
public static void setEnabled(boolean enabled) {
tracker.setEnabled(enabled);
}
private static String getDeploymentMethod() {
return getApplicationDeployment() == null ? "fatjar" : getApplicationDeployment();
}
private static String getJavaVersionIdentifier() {
return System.getProperty("java.runtime.name") + " " + System.getProperty("java.version");
}
private static AnalyticsConfigData getConfig(String webPropertyID, VisitorData visitorData) {
AnalyticsConfigData config = new AnalyticsConfigData(webPropertyID, visitorData);
config.setUserAgent(getUserAgent());
config.setEncoding(System.getProperty("file.encoding"));
config.setUserLanguage(getUserLanguage());
try {
GraphicsDevice[] display = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
config.setScreenResolution(getScreenResolution(display));
config.setColorDepth(getColorDepth(display));
} catch (HeadlessException e) {
Logger.getLogger(Analytics.class.getName()).warning(e.getMessage());
config.setScreenResolution("80x25");
config.setColorDepth("1");
}
return config;
}
private static String getUserAgent() {
String wm = null;
String os = null;
if (Platform.isWindows()) {
wm = "Windows";
os = "Windows NT " + System.getProperty("os.version");
} else if (Platform.isX11()) {
wm = "X11";
if (Platform.isLinux())
os = "Linux " + System.getProperty("os.arch");
else if (Platform.isSolaris())
os = "SunOS " + System.getProperty("os.version");
else if (Platform.isFreeBSD())
os = "FreeBSD";
else if (Platform.isOpenBSD())
os = "OpenBSD";
} else if (Platform.isMac()) {
wm = "Macintosh";
os = System.getProperty("os.name");
}
return String.format("%s/%s (%s; U; %s; JRE %s)", getApplicationName(), getApplicationVersion(), wm, os, System.getProperty("java.version"));
}
private static String getUserLanguage() {
String region = System.getProperty("user.region");
if (region == null)
region = System.getProperty("user.country");
return System.getProperty("user.language") + "-" + region;
}
private static String getScreenResolution(GraphicsDevice[] display) {
int screenHeight = 0;
int screenWidth = 0;
// get size of each screen
for (int i = 0; i < display.length; i++) {
DisplayMode dm = display[i].getDisplayMode();
screenWidth += dm.getWidth();
screenHeight += dm.getHeight();
}
if (screenHeight <= 0 && screenWidth <= 0)
throw new HeadlessException("Illegal screen size");
return screenWidth + "x" + screenHeight;
}
private static String getColorDepth(GraphicsDevice[] display) {
if (display[0] == null)
return null;
String colorDepth = display[0].getDisplayMode().getBitDepth() + "";
for (int i = 1; i < display.length; i++) {
colorDepth += ", " + display[i].getDisplayMode().getBitDepth();
}
return colorDepth;
}
private static VisitorData restoreVisitorData() {
try {
// try to restore visitor
int visitorId = Integer.parseInt(persistentData.get(VISITOR_ID));
long timestampFirst = Long.parseLong(persistentData.get(TIMESTAMP_FIRST));
long timestampLast = Long.parseLong(persistentData.get(TIMESTAMP_LAST));
int visits = Integer.parseInt(persistentData.get(VISITS));
return VisitorData.newSession(visitorId, timestampFirst, timestampLast, visits);
} catch (Exception e) {
// new visitor
return VisitorData.newVisitor();
}
}
private static void storeVisitorData(VisitorData visitor) {
persistentData.put(VISITOR_ID, String.valueOf(visitor.getVisitorId()));
persistentData.put(TIMESTAMP_FIRST, String.valueOf(visitor.getTimestampFirst()));
persistentData.put(TIMESTAMP_LAST, String.valueOf(visitor.getTimestampPrevious()));
persistentData.put(VISITS, String.valueOf(visitor.getVisits()));
}
public static void completeTracking(long timeout) {
storeVisitorData(visitorData);
JGoogleAnalyticsTracker.completeBackgroundTasks(timeout);
}
static {
Runtime.getRuntime().addShutdownHook(new Thread("AnalyticsShutdownHook") {
@Override
public void run() {
completeTracking(2000);
}
});
}
/**
* Dummy constructor to prevent instantiation.
*/
private Analytics() {
throw new UnsupportedOperationException();
}
}

View File

@ -71,12 +71,16 @@ public class Main {
CacheManager.getInstance().clearAll();
}
// initialize analytics
Analytics.setEnabled(!argumentBean.disableAnalytics);
// run command-line interface and then exit
if (argumentBean.runCLI()) {
// run cmdline interface and then exit
System.exit(new ArgumentProcessor().process(argumentBean));
int status = new ArgumentProcessor().process(argumentBean);
System.exit(status);
}
// Start user interface
// start user interface
SwingUtilities.invokeLater(new Runnable() {
@Override

View File

@ -32,9 +32,21 @@ public final class Settings {
}
public static String getApplicationDeployment() {
String deployment = System.getProperty("application.deployment");
if (deployment != null)
return deployment;
if (System.getProperty("javawebstart.version") != null)
return "webstart";
return null;
}
public static File getApplicationFolder() {
// special handling for web start
if (System.getProperty("application.deployment") != null || System.getProperty("javawebstart.version") != null) {
if (getApplicationDeployment() != null) {
// can't use working directory for web start applications
File folder = new File(System.getProperty("user.home"), ".filebot");

View File

@ -2,6 +2,10 @@
application.name: FileBot
application.version: 2.0
# google analytics
analytics.WebPropertyID: UA-25379256-2
# database api keys
thetvdb.apikey: 58B4AA94C59AD656
themoviedb.apikey: 5a6edae568130bf10617b6d45be99f13
sublight.apikey: afa9ecb2-a3ee-42b1-9225-000b4038bc85

View File

@ -57,11 +57,14 @@ public class ArgumentBean {
@Option(name = "--log", usage = "Log level", metaVar = "[all, config, info, warning]")
public String log = "all";
@Option(name = "-open", usage = "Open file in GUI", metaVar = "file")
public boolean open = false;
@Option(name = "-clear", usage = "Clear cache and application settings")
public boolean clear = false;
@Option(name = "-open", usage = "Open file in GUI", metaVar = "file")
public boolean open = false;
@Option(name = "-no-analytics", usage = "Disable analytics")
public boolean disableAnalytics = false;
@Option(name = "-help", usage = "Print this help message")
public boolean help = false;

View File

@ -28,10 +28,11 @@ import java.util.TreeSet;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.format.MediaBindingBean;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.format.MediaBindingBean;
import net.sourceforge.filebot.hash.HashType;
import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.hash.VerificationFileWriter;
@ -60,8 +61,10 @@ import net.sourceforge.filebot.web.VideoHashSubtitleService;
public class ArgumentProcessor {
public int process(ArgumentBean args) throws Exception {
Analytics.trackView(ArgumentProcessor.class, "FileBot CLI");
CLILogger.setLevel(args.getLogLevel());
try {
CLILogger.setLevel(args.getLogLevel());
Set<File> files = new LinkedHashSet<File>(args.getFiles(true));
if (args.getSubtitles) {
@ -185,6 +188,7 @@ public class ArgumentProcessor {
}
// rename episodes
Analytics.trackEvent("CLI", "Rename", "Episode", renameMap.size());
return renameAll(renameMap);
}
@ -243,7 +247,8 @@ public class ArgumentProcessor {
}
}
// rename episodes
// rename movies
Analytics.trackEvent("CLI", "Rename", "Movie", renameMap.size());
return renameAll(renameMap);
}
@ -279,6 +284,7 @@ public class ArgumentProcessor {
if (it.getValue() != null && it.getValue().size() > 0) {
// auto-select first element if there are multiple hash matches for the same video files
File subtitle = fetchSubtitle(it.getValue().get(0), it.getKey(), outputFormat, outputEncoding);
Analytics.trackEvent(service.getName(), "DownloadSubtitle", it.getValue().get(0).getLanguageName(), 1);
// download complete, cross this video off the list
remainingVideos.remove(it.getKey());
@ -305,6 +311,7 @@ public class ArgumentProcessor {
for (SubtitleDescriptor descriptor : subtitles) {
if (isDerived(descriptor.getName(), video)) {
File subtitle = fetchSubtitle(descriptor, video, outputFormat, outputEncoding);
Analytics.trackEvent(service.getName(), "DownloadSubtitle", descriptor.getLanguageName(), 1);
// download complete, cross this video off the list
remainingVideos.remove(video);
@ -324,6 +331,7 @@ public class ArgumentProcessor {
CLILogger.warning("No matching subtitles found: " + video);
}
Analytics.trackEvent("CLI", "Download", "Subtitle", downloadedSubtitles.size());
return downloadedSubtitles;
}

View File

@ -27,6 +27,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanelBuilder;
@ -131,6 +132,7 @@ public class MainFrame extends JFrame {
contentPane.add(panel);
}
Analytics.trackView(panel.getClass(), selectedBuilder.getName());
headerPanel.setTitle(selectedBuilder.getName());
panel.setVisible(true);
}

View File

@ -29,6 +29,7 @@ import java.util.concurrent.RunnableFuture;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
@ -122,6 +123,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
SearchResult selectedSearchResult = selectSearchResult(query, results);
if (selectedSearchResult != null) {
Analytics.trackEvent(provider.getName(), "FetchEpisodeList", selectedSearchResult.getName());
return provider.getEpisodeList(selectedSearchResult, locale);
}
}
@ -194,6 +196,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
}
});
Analytics.trackEvent(provider.getName(), "Match", "Episode", matches.size());
return matches;
}
}

View File

@ -30,6 +30,7 @@ import java.util.concurrent.RunnableFuture;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.MovieDescriptor;
@ -65,6 +66,10 @@ class MovieHashMatcher implements AutoCompleteMatcher {
// unknown hash, try via imdb id from nfo file
if (movie == null || !autodetect) {
movie = grabMovieName(movieFiles[i], locale, autodetect, movie);
if (movie != null) {
Analytics.trackEvent(service.getName(), "SearchMovie", movie.getName());
}
}
// check if we managed to lookup the movie descriptor
@ -81,6 +86,8 @@ class MovieHashMatcher implements AutoCompleteMatcher {
}
}
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", filesByMovie.size()); // number of positive hash lookups
// collect all File/MoviePart matches
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
@ -125,6 +132,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
}
});
Analytics.trackEvent(service.getName(), "Match", "Movie", matches.size());
return matches;
}

View File

@ -12,6 +12,7 @@ import java.awt.event.ActionEvent;
import java.io.File;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@ -19,6 +20,7 @@ import java.util.Map.Entry;
import javax.swing.AbstractAction;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager;
@ -74,10 +76,14 @@ class RenameAction extends AbstractAction {
}
}
// collect renamed types
List<Class> types = new ArrayList<Class>();
// remove renamed matches
for (Entry<File, ?> entry : renameLog) {
// find index of source file
int index = model.files().indexOf(entry.getKey());
types.add(model.values().get(index).getClass());
// remove complete match
model.matches().remove(index);
@ -86,6 +92,10 @@ class RenameAction extends AbstractAction {
// update history
if (renameLog.size() > 0) {
HistorySpooler.getInstance().append(renameLog);
for (Class it : new HashSet<Class>(types)) {
Analytics.trackEvent("GUI", "Rename", it.getSimpleName(), frequency(types, it));
}
}
}

View File

@ -52,6 +52,7 @@ import ca.odell.glazedlists.swing.EventSelectionModel;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.subtitle.SubtitleFormat;
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePackage.Download.Phase;
@ -239,7 +240,9 @@ class SubtitleDownloadComponent extends JComponent {
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == Phase.DONE) {
try {
files.addAll(subtitle.getDownload().get());
List<MemoryFile> subtitles = subtitle.getDownload().get();
Analytics.trackEvent(subtitle.getProvider().getName(), "DownloadSubtitle", subtitle.getLanguage().getName(), subtitles.size());
files.addAll(subtitles);
} catch (CancellationException e) {
// ignore cancellation
} catch (Exception e) {

View File

@ -24,19 +24,20 @@ import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.vfs.ArchiveType;
import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.filebot.web.SubtitleProvider;
import net.sourceforge.tuned.FileUtilities;
public class SubtitlePackage {
private final SubtitleProvider provider;
private final SubtitleDescriptor subtitle;
private final Language language;
private Download download;
public SubtitlePackage(SubtitleDescriptor subtitle) {
public SubtitlePackage(SubtitleProvider provider, SubtitleDescriptor subtitle) {
this.provider = provider;
this.subtitle = subtitle;
// resolve language name
@ -58,6 +59,11 @@ public class SubtitlePackage {
}
public SubtitleProvider getProvider() {
return provider;
}
public String getName() {
return subtitle.getName();
}

View File

@ -214,7 +214,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
List<SubtitlePackage> packages = new ArrayList<SubtitlePackage>();
for (SubtitleDescriptor subtitle : request.getProvider().getSubtitleList(getSearchResult(), request.getLanguageName())) {
packages.add(new SubtitlePackage(subtitle));
packages.add(new SubtitlePackage(request.getProvider(), subtitle));
}
return packages;

View File

@ -52,6 +52,7 @@ import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.filebot.web.VideoHashSubtitleService;
@ -161,7 +162,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
// query services sequentially
queryService = Executors.newFixedThreadPool(1);
for (VideoHashSubtitleServiceBean service : services) {
for (final VideoHashSubtitleServiceBean service : services) {
QueryTask task = new QueryTask(service, mappingModel.getVideoFiles(), languageName) {
@Override
@ -179,6 +180,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
}
// make subtitle column visible
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", subtitles.size()); // number of positive hash lookups
mappingModel.setOptionColumnVisible(true);
} catch (Exception e) {
Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage());
@ -584,7 +586,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
private static class SubtitleDescriptorBean extends AbstractBean {
private final SubtitleDescriptor subtitle;
private final VideoHashSubtitleServiceBean source;
private final VideoHashSubtitleServiceBean service;
private StateValue state;
private Exception error;
@ -592,7 +594,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
public SubtitleDescriptorBean(SubtitleDescriptor subtitle, VideoHashSubtitleServiceBean source) {
this.subtitle = subtitle;
this.source = source;
this.service = source;
}
@ -602,7 +604,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
public Icon getIcon() {
return source.getIcon();
return service.getIcon();
}
@ -615,6 +617,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
setState(StateValue.STARTED);
try {
Analytics.trackEvent(service.getName(), "DownloadSubtitle", subtitle.getLanguageName(), 1);
return subtitle.fetch();
} catch (Exception e) {
// remember exception