Java tutorial
/* * App.java - Copyright(c) 2013 Joe Pasqua * Provided under the MIT License. See the LICENSE file for details. * Created: Aug 30, 2013 */ package org.noroomattheinn.visibletesla; import com.sun.net.httpserver.BasicAuthenticator; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.List; import java.util.Properties; import javafx.application.Application; import javafx.application.HostServices; import javafx.application.Platform; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.control.Tab; import javafx.scene.input.InputEvent; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.stage.Stage; import org.apache.commons.codec.digest.DigestUtils; import org.noroomattheinn.tesla.Tesla; import org.noroomattheinn.utils.PWUtils; import org.noroomattheinn.utils.RestHelper; import org.noroomattheinn.utils.ThreadManager; import org.noroomattheinn.utils.TrackedObject; import org.noroomattheinn.utils.Utils; import static org.noroomattheinn.tesla.Tesla.logger; import static org.noroomattheinn.utils.Utils.timeSince; /** * App - Stores state about the app for use across the app. This is a singleton * that is created at app startup. * * @author Joe Pasqua <joe at NoRoomAtTheInn dot org> */ class App { /*------------------------------------------------------------------------------ * * Constants and Enums * *----------------------------------------------------------------------------*/ private static final String AppPropertiesFile = "org/noroomattheinn/visibletesla/VT.properties"; static final String LastExportDirKey = "APP_LAST_EXPORT_DIR"; /*------------------------------------------------------------------------------ * * Internal State * *----------------------------------------------------------------------------*/ private final File appFilesFolder; private final Prefs prefs; private final Application fxApp; private final PWUtils pwUtils = new PWUtils(); private byte[] encPW, salt; private long lastEventTime; private static Properties appProperties = loadAppProperties( App.class.getClassLoader().getResourceAsStream(AppPropertiesFile)); /*------------------------------------------------------------------------------ * * Package-wide State * *----------------------------------------------------------------------------*/ final AppAPI api; final Tesla tesla; final Stage stage; final ProgressListener progressListener; final TrackedObject<String> schedulerActivity; /*============================================================================== * ------- ------- * ------- Package Internal Interface To This Class ------- * ------- ------- *============================================================================*/ /** * Called once when the app starts in order to create the singleton. * @param fxApp The JavaFX Application object * @param stage The JavaFX stage corresponding to our base window * @param prefs The application preferences * @return The newly created singleton. */ App(Application fxApp, Stage stage, final Prefs prefs) { this.fxApp = fxApp; this.stage = stage; this.prefs = prefs; this.schedulerActivity = new TrackedObject<>(""); this.api = new AppAPI(schedulerActivity); this.lastEventTime = System.currentTimeMillis(); api.mode.addTracker(new Runnable() { @Override public void run() { logger.finest("App Mode changed to " + api.mode.get()); prefs.persist("InactivityMode", api.mode.get().name()); if (api.mode.get() == AppAPI.Mode.StayAwake) { api.setActive(); } } }); api.state.addTracker(new Runnable() { @Override public void run() { logger.finest("App State changed to " + api.state.get()); if (api.state.get() == AppAPI.State.Active) { logger.info("Resetting Idle start time to now"); lastEventTime = System.currentTimeMillis(); } } }); appFilesFolder = Utils.ensureAppFilesFolder(productName()); Utils.setupLogger(appFilesFolder, "visibletesla", logger, prefs.getLogLevel()); if (prefs.enableProxy.get()) { // Enable the proxy at the lowest level so all services use it RestHelper.setDefaultProxy(prefs.proxyHost.get(), prefs.proxyPort.get()); } tesla = new Tesla(); this.progressListener = new ProgressListener(prefs.submitAnonFailure, getAppID()); internalizePW(prefs.authCode.get()); } static String productName() { return appProperties.getProperty("APP_NAME"); } static String productVersion() { return appProperties.getProperty("APP_VERSION"); } void showDocument(String doc) { fxApp.getHostServices().showDocument(doc); } HostServices getHostServices() { return fxApp.getHostServices(); } /** * Establish ourselves as the only running instance of the app for * a particular vehicle id. * @return true is we got the lock * false if another instance is already running */ boolean lock(String vin) { return (Utils.obtainLock(vin + ".lck", appFilesFolder)); } /** * Get the system folder in which app related files are to be stored. * @return The folder in which app related files are to be stored */ File appFileFolder() { return appFilesFolder; } final String getAppID() { String appID = "Unidentified"; try { InetAddress ip = InetAddress.getLocalHost(); NetworkInterface network = NetworkInterface.getByInetAddress(ip); if (network != null) { byte[] mac = network.getHardwareAddress(); appID = DigestUtils.sha256Hex(mac); } } catch (UnknownHostException | SocketException e) { logger.warning("Unable to generate an AppID: " + e.getMessage()); } return appID; } /** * Add a tracker to a TrackedObject, but ensure it will run on the * FX Application Thread. * @param t The tracked object * @param r The Runnable to execute on the FXApplicationThread */ static void addTracker(TrackedObject t, final Runnable r) { t.addTracker(new Runnable() { @Override public void run() { Platform.runLater(r); } }); } /** * Set the mode based on the value in the persistent store */ void restoreMode() { String modeName = prefs.storage().get("InactivityMode", AppAPI.Mode.StayAwake.name()); // Handle obsolete values or changed names switch (modeName) { case "Sleep": modeName = "AllowSleeping"; break; // Name Changed case "Awake": modeName = "StayAwake"; break; // Name Changed case "AllowDaydreaming": modeName = "Awake"; break; // Obsolete case "Daydream": modeName = "Awake"; break; // Obsolete } api.mode.set(AppAPI.Mode.valueOf(modeName)); } /*------------------------------------------------------------------------------ * * Support for authenticating to web services * *----------------------------------------------------------------------------*/ /** * Set the password used by the RESTServer. If no password is supplied, * a random one will be chosen meaning there is effectively no access to * the server. * @param pw The new password * @return An external representation of the salted password that can be * stored safely in a data file. */ final String setPW(String pw) { if (pw == null || pw.isEmpty()) { // Choose a random value! pw = String.valueOf(Math.floor(Math.random() * 100000)); } salt = pwUtils.generateSalt(); encPW = pwUtils.getEncryptedPassword(pw, salt); return pwUtils.externalRep(salt, encPW); } BasicAuthenticator authenticator = new BasicAuthenticator("VisibleTesla") { @Override public boolean checkCredentials(String user, String pwd) { if (!user.equals("VT")) return false; if (encPW == null || salt == null) return false; return pwUtils.authenticate(pwd, encPW, salt); } }; /** * Initialize the password and salt from previously generated values. * @param externalForm An external representation of the password and * salt that was previously returned by an invocation * of setPW() */ private void internalizePW(String externalForm) { // Break down the external representation into the salt and password List<byte[]> internalForm = (new PWUtils()).internalRep(externalForm); salt = internalForm.get(0); encPW = internalForm.get(1); } /*------------------------------------------------------------------------------ * * PRIVATE - Methods and classes to track activity * *----------------------------------------------------------------------------*/ /** * Begin watching for user inactivity (keyboard input, mouse movements, etc.) * on any of the specified Tabs. * @param tabs Watch for user activity targeted to any of these tabs. */ void watchForUserActivity(List<Tab> tabs) { for (Tab t : tabs) { Node n = t.getContent(); n.addEventFilter(KeyEvent.ANY, new EventPassThrough()); n.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventPassThrough()); n.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventPassThrough()); } ThreadManager.get().launch(new InactivityThread(60L * 1000L * prefs.idleThresholdInMinutes.get()), "Inactivity"); } private class InactivityThread implements Runnable { long idleThreshold; InactivityThread(long threshold) { this.idleThreshold = threshold; } @Override public void run() { while (true) { ThreadManager.get().sleep(60 * 1000); if (ThreadManager.get().shuttingDown()) { return; } if (timeSince(lastEventTime) > idleThreshold && api.allowingSleeping()) { api.state.update(AppAPI.State.Idle); } } } } private class EventPassThrough implements EventHandler<InputEvent> { @Override public void handle(InputEvent ie) { lastEventTime = System.currentTimeMillis(); api.state.update(AppAPI.State.Active); } } private static Properties loadAppProperties(InputStream is) { Properties p = new Properties(); try { p.load(is); } catch (IOException ex) { logger.warning("Couldn't load app properties file: " + ex); } return p; } }