Skip to content
Snippets Groups Projects
TablexiaRaven.java 13.7 KiB
Newer Older
package cz.nic.tablexia;

import com.badlogic.gdx.Gdx;
import com.getsentry.raven.DefaultRavenFactory;
import com.getsentry.raven.Raven;
import com.getsentry.raven.connection.Connection;
import com.getsentry.raven.connection.EventSendFailureCallback;
import com.getsentry.raven.dsn.Dsn;
import com.getsentry.raven.event.Event;
import com.getsentry.raven.event.EventBuilder;
import com.getsentry.raven.event.helper.EventBuilderHelper;
import com.getsentry.raven.event.interfaces.ExceptionInterface;
import com.getsentry.raven.event.interfaces.SentryInterface;
import com.getsentry.raven.event.interfaces.StackTraceInterface;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import cz.nic.tablexia.bus.ApplicationBus;
import cz.nic.tablexia.game.AbstractTablexiaGame;
import cz.nic.tablexia.loader.TablexiaAbstractFileManager;
import cz.nic.tablexia.screen.AbstractTablexiaScreen;
/**
 * Created by drahomir on 7/28/16.
 */
public class TablexiaRaven {
    private static final String EXCEPTION_TYPE_TAG_NAME = "ExceptionType";

    public enum ExceptionType {
        JavaException,
        IOSException
    }

    /**
     * Custom raven factory which allows to set EventSendFailureCallback
     */
    private class TablexiaRavenFactory extends DefaultRavenFactory {
        private EventSendFailureCallback eventSendFailureCallback;

        public Raven ravenInstance(String DSN, EventSendFailureCallback callback) {
            eventSendFailureCallback = callback;
            return createRavenInstance(new Dsn(DSN));
        }

        @Override
        protected Connection createConnection(Dsn dsn) {
            Connection connection = super.createConnection(dsn);

            if(eventSendFailureCallback != null) connection.addEventSendFailureCallback(eventSendFailureCallback);

            return connection;
        }
    }

    /**
     * Type of information to send via raven
     */
    private enum InfoType {
        TAG {
            @Override
            protected void insertInfo(TablexiaEventBuilderHelper tablexiaEventBuilderHelper, String name, String value) {
                tablexiaEventBuilderHelper.addTag(name, value);
            protected void insertInfo(TablexiaEventBuilderHelper tablexiaEventBuilderHelper, String name, String value) {
                tablexiaEventBuilderHelper.addExtra(name, value);
        protected void insertInfo(TablexiaEventBuilderHelper tablexiaEventBuilderHelper, String name, String value) {}
    }

    /**
     * Actual info values to send via raven
     */
    private enum Info {
        Platform(InfoType.TAG, "Platform", new StringRunnable() {
            @Override
            public String run() {
                return Gdx.app.getType().toString();
            }
        }),
        Version(InfoType.TAG, "Version", new StringRunnable() {
            @Override
            public String run() {
                return TablexiaBuildConfig.VERSION_NAME;
            }
        }),
        BuildType(InfoType.TAG, "BuildType", new StringRunnable() {
            @Override
            public String run() {
                return TablexiaSettings.getInstance().getBuildType().toString();
            }
        }),
        ConnectionType(InfoType.TAG, "ConnectionType", new StringRunnable() {
            @Override
            public String run() {
                return Tablexia.getConnectionManager().getConnectionType().toString();
            }
        }),
        Language(InfoType.EXTRA, "Language", new StringRunnable() {
            @Override
            public String run() {
                return TablexiaSettings.getInstance().getLocale().toString();
            }
        }),
        UserInfo(InfoType.EXTRA, "UserInfo", new StringRunnable() {
            @Override
            public String run() {
                return TablexiaSettings.getInstance().getSelectedUser().toString();
            }
        }),
        UserUUID(InfoType.EXTRA, "UserUUID", new StringRunnable() {
            @Override
            public String run() {
                return TablexiaSettings.getInstance().getSelectedUser().getUuid();
            }
        });

        private static final String DEFAULT_FALLBACK_VALUE = "UNDEFINED";

        private final InfoType type;
        private final String name;
        private final String fallBackValue;
        private final StringRunnable stringRunnable;

        Info(InfoType type, String name, StringRunnable stringRunnable) {
            this(type, name, DEFAULT_FALLBACK_VALUE, stringRunnable);
        }

        Info(InfoType type, String name, String fallbackValue, StringRunnable stringRunnable) {
            this.type = type;
            this.name = name;
            this.fallBackValue = fallbackValue;
            this.stringRunnable = stringRunnable;
        }

        public void insert(TablexiaEventBuilderHelper eventBuilderHelper) {
            type.insertInfo(eventBuilderHelper, name, getSafeValue(stringRunnable, fallBackValue));
        }

        private String getSafeValue(StringRunnable runnable, String defaultValue) {
            try {
                return runnable.run();
            }
            catch (Exception e) {
                return defaultValue;
            }
        }
    }

    private interface StringRunnable {
        //Method which obtains the actual value of Info
    /**
     * Reports Manager - Saves and Resends reports later
     */
    private static class ReportsManager extends TablexiaAbstractFileManager {
        private static final String  REPORT_FILE_EXTENSION = ".TablexiaReport";
        private static final boolean HIDE_REPORT_FILES     = true;

        public ReportsManager() {
            super(ReportStorageType.EXTERNAL);
        }

        public void storeRavenEvent(Event event) {
            try {
                String fileName  = (HIDE_REPORT_FILES ? "." : "") + event.getId().toString() + REPORT_FILE_EXTENSION;

                File dir = TablexiaAbstractFileManager.getFileStoragePathFileHandle(ReportStorageType.EXTERNAL).file();
                if(!dir.exists()) dir.mkdir();

                File file = new File(dir, fileName);
                if(file.exists()) {
                    file.delete();
                    file.createNewFile();
                }

                FileOutputStream fileOut = new FileOutputStream(file);
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOut);
                objectOutputStream.writeObject(event);
                objectOutputStream.close();
                fileOut.close();
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
        }

        private void sendSavedEvents(TablexiaRaven tablexiaRaven) {
            try {
                File dir = TablexiaAbstractFileManager.getFileStoragePathFileHandle(ReportStorageType.EXTERNAL).file();
                if(!dir.exists()) return;

                File[] files = dir.listFiles();

                for(File file : files) {
                    Event e = deserializeEvent(file);
                    if(e != null) {
                        file.delete();
                        tablexiaRaven.sendEvent(e);
                    }
                }
            }
            catch (Exception e) {
                e.printStackTrace();
                return;
            }
        }

        private Event deserializeEvent(File file) {
            Event event = null;

            try {
                FileInputStream fis = new FileInputStream(file);
                ObjectInputStream ois = new ObjectInputStream(fis);

                event = (Event) ois.readObject();
                ois.close();
                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            return event;
        }
    }

    public class TablexiaEventBuilderHelper implements EventBuilderHelper {
        private HashMap<String, String> tablexiaTags;
        private HashMap<String, String> tablexiaExtras;

        public TablexiaEventBuilderHelper() {
            tablexiaTags   = new HashMap<String, String>();
            tablexiaExtras = new HashMap<String, String>();
        }

        public void addTag(String key, String value) {
            tablexiaTags.put(key, value);
        }

        public void addExtra(String key, String value) {
            tablexiaExtras.put(key, value);
        }

        public void removeTag(String key) {
            if(tablexiaTags.containsKey(key))
                tablexiaTags.remove(key);
        }

        public void removeExtra(String key) {
            if(tablexiaExtras.containsKey(key))
                tablexiaExtras.remove(key);
        }

        @Override
        public void helpBuildingEvent(EventBuilder eventBuilder) {
            for(Map.Entry<String, String> tag : tablexiaTags.entrySet()) {
                eventBuilder.withTag(tag.getKey(), tag.getValue());
            }

            for(Map.Entry<String, String> extra : tablexiaExtras.entrySet()) {
                eventBuilder.withExtra(extra.getKey(), extra.getValue());
            }
        }
    }

    private static final String[] SCREEN_INFO_EVENT_KEYS = new String [] {
            AbstractTablexiaGame.RANDOM_SEED_SCREEN_INFO_LABEL,
            AbstractTablexiaGame.GAME_DIFFICULTY_SCREEN_INFO_LABEL
    };

    private static TablexiaRaven instance;

    private final EventSendFailureCallback DEFAULT_SEND_EVENT_FAILURE_CALLBACK = new EventSendFailureCallback() {
        @Override
        public void onFailure(Event event, Exception exception) {
            //Calls all registered callbacks
            for(EventSendFailureCallback eventSendFailureCallback : sendFailureCallbacks) eventSendFailureCallback.onFailure(event, exception);
        }
    };

    private Set<EventSendFailureCallback> sendFailureCallbacks;
    private Raven raven;

    private TablexiaEventBuilderHelper tablexiaEventBuilderHelper;
    private TablexiaRaven(String DSN) {
        this.raven = new TablexiaRavenFactory().ravenInstance(DSN, DEFAULT_SEND_EVENT_FAILURE_CALLBACK);
        this.sendFailureCallbacks = new HashSet<EventSendFailureCallback>();
        this.tablexiaEventBuilderHelper = new TablexiaEventBuilderHelper();

        raven.addBuilderHelper(tablexiaEventBuilderHelper);

        ApplicationBus.getInstance().subscribe(this);
    private static boolean isStarted() {
        return instance != null;
    }
    public static void start(String DSN) {
        if(DSN == null || instance != null) return;
        instance = new TablexiaRaven(DSN);

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                instance.sendExceptionEvent(t, e);
            }
        });

        instance.addEventSendFailureCallback(new EventSendFailureCallback() {
            @Override
            public void onFailure(Event event, Exception exception) {
                instance.reportsManager.storeRavenEvent(event);
            }
        });
    public static void sendSavedReports() {
        if(isStarted()) instance.reportsManager.sendSavedEvents(instance);
    private void addEventSendFailureCallback(EventSendFailureCallback eventSendFailureCallback) {
        if(isStarted()) {
            if (eventSendFailureCallback != null) {
                if (!sendFailureCallbacks.contains(eventSendFailureCallback))
                    sendFailureCallbacks.add(eventSendFailureCallback);
            }
    private void buildAndSendExceptionEvent(String message, SentryInterface sentryInterface, ExceptionType exceptionType) {
        EventBuilder eventBuilder = new EventBuilder()
                .withMessage(message)
                .withSentryInterface(sentryInterface)
                .withLevel(Event.Level.ERROR)
                .withTag(EXCEPTION_TYPE_TAG_NAME, exceptionType.toString());
        for(Info info : Info.values()) {
        raven.runBuilderHelpers(eventBuilder);
        raven.sendEvent(eventBuilder.build());
    private void sendExceptionEvent(Thread t, Throwable e) {
        buildAndSendExceptionEvent(e.getMessage(), new ExceptionInterface(e), ExceptionType.JavaException);
    }

    public static void sendCustomException(ExceptionType exceptionType, String msg, StackTraceElement[] stackTraceElements) {
        if(!isStarted()) return;
        instance.buildAndSendExceptionEvent(msg, new StackTraceInterface(stackTraceElements), exceptionType);
    }

    private void sendEvent(Event e) {
        if(isStarted() && e != null) instance.raven.sendEvent(e);

    @Handler
    public void handleScreenChangedEvent(TablexiaApplication.ScreenChangedEvent screenChangedEvent) {
        for(String infoEventKey : SCREEN_INFO_EVENT_KEYS) {
            tablexiaEventBuilderHelper.removeExtra(infoEventKey);
        }
    }

    @Handler
    public void handleScreenInfoEvent(AbstractTablexiaScreen.ScreenInfoEvent screenInfoEvent) {
        for(String infoEventKey : SCREEN_INFO_EVENT_KEYS) {
            if(infoEventKey.equals(screenInfoEvent.getInfoKey())) {
                tablexiaEventBuilderHelper.addExtra(screenInfoEvent.getInfoKey(), screenInfoEvent.getInfoValue());
            }
        }
    }