User config in _ext_mpw, global config.json & residence config.
Moved user preferences (default type & hide passwords) into _ext_mpw. Fixed an issue with JSON serialization of any values. Made update check & background residency globally configurable preferences saved in config.json.
This commit is contained in:
		@@ -147,7 +147,7 @@ public class MPMasterKey {
 | 
			
		||||
     * @return {@code null} if the result type is missing a required parameter.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
 | 
			
		||||
     * @throws MPAlgorithmException An internal system or algorithm error has occurred.
 | 
			
		||||
     * @throws MPAlgorithmException      An internal system or algorithm error has occurred.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
 | 
			
		||||
@@ -185,7 +185,7 @@ public class MPMasterKey {
 | 
			
		||||
     *                    {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
 | 
			
		||||
     * @throws MPAlgorithmException An internal system or algorithm error has occurred.
 | 
			
		||||
     * @throws MPAlgorithmException      An internal system or algorithm error has occurred.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPConfig;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPModelConstants;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -26,15 +27,32 @@ import com.lyndir.masterpassword.model.MPModelConstants;
 | 
			
		||||
 * @author lhunath, 2014-08-31
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("CallToSystemGetenv")
 | 
			
		||||
public class MPConfig {
 | 
			
		||||
public class MPGuiConfig extends MPConfig {
 | 
			
		||||
 | 
			
		||||
    private static final MPConfig instance = new MPConfig();
 | 
			
		||||
 | 
			
		||||
    public static MPConfig get() {
 | 
			
		||||
        return instance;
 | 
			
		||||
    public static MPGuiConfig get() {
 | 
			
		||||
        return get( MPGuiConfig.class );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Boolean checkForUpdates;
 | 
			
		||||
    Boolean stayResident;
 | 
			
		||||
 | 
			
		||||
    public boolean checkForUpdates() {
 | 
			
		||||
        return ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
 | 
			
		||||
        return (checkForUpdates != null)? checkForUpdates:
 | 
			
		||||
                ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setCheckForUpdates(final boolean checkForUpdates) {
 | 
			
		||||
        this.checkForUpdates = checkForUpdates;
 | 
			
		||||
        MasterPassword.get().updateCheck();
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean stayResident() {
 | 
			
		||||
        return (stayResident != null)? stayResident: false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setStayResident(final boolean stayResident) {
 | 
			
		||||
        this.stayResident = stayResident;
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -46,10 +46,10 @@ import javax.swing.*;
 | 
			
		||||
public final class MasterPassword {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("UnusedDeclaration")
 | 
			
		||||
    private static final Logger logger = Logger.get( MasterPassword.class );
 | 
			
		||||
 | 
			
		||||
    private static final Logger         logger   = Logger.get( MasterPassword.class );
 | 
			
		||||
    private static final MasterPassword instance = new MasterPassword();
 | 
			
		||||
 | 
			
		||||
    private final Provider             keyMaster = Provider.getCurrentProvider( true );
 | 
			
		||||
    private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -97,7 +97,29 @@ public final class MasterPassword {
 | 
			
		||||
        } );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void checkUpdate() {
 | 
			
		||||
    public static void main(final String... args) {
 | 
			
		||||
        //Thread.setDefaultUncaughtExceptionHandler(
 | 
			
		||||
        //        (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
 | 
			
		||||
 | 
			
		||||
        // Set the system look & feel, if available.
 | 
			
		||||
        try {
 | 
			
		||||
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
 | 
			
		||||
        }
 | 
			
		||||
        catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create and open the UI.
 | 
			
		||||
        get().open();
 | 
			
		||||
 | 
			
		||||
        // UI features.
 | 
			
		||||
        get().updateResidence();
 | 
			
		||||
        get().updateCheck();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateCheck() {
 | 
			
		||||
        if (!MPGuiConfig.get().checkForUpdates())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            String implementationVersion = version();
 | 
			
		||||
            String latestVersion = new ByteSource() {
 | 
			
		||||
@@ -127,26 +149,10 @@ public final class MasterPassword {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(final String... args) {
 | 
			
		||||
        //Thread.setDefaultUncaughtExceptionHandler(
 | 
			
		||||
        //        (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
 | 
			
		||||
 | 
			
		||||
        // Try and set the system look & feel, if available.
 | 
			
		||||
        try {
 | 
			
		||||
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
 | 
			
		||||
            Platform.get().installAppForegroundHandler( get()::open );
 | 
			
		||||
            Platform.get().installAppReopenHandler( get()::open );
 | 
			
		||||
            Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
 | 
			
		||||
        }
 | 
			
		||||
        catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Create a platform-specific GUI and open it.
 | 
			
		||||
        get().open();
 | 
			
		||||
 | 
			
		||||
        // Check online to see if this version has been superseded.
 | 
			
		||||
        if (MPConfig.get().checkForUpdates())
 | 
			
		||||
            get().checkUpdate();
 | 
			
		||||
    public void updateResidence() {
 | 
			
		||||
        Platform.get().installAppForegroundHandler( get()::open );
 | 
			
		||||
        Platform.get().installAppReopenHandler( get()::open );
 | 
			
		||||
        keyMaster.register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
 | 
			
		||||
 
 | 
			
		||||
@@ -17,24 +17,47 @@ public class ApplePlatform implements IPlatform {
 | 
			
		||||
    private static final Application application = Preconditions.checkNotNull(
 | 
			
		||||
            Application.getApplication(), "Not an Apple Java application." );
 | 
			
		||||
 | 
			
		||||
    private AppForegroundListener appForegroundHandler;
 | 
			
		||||
    private AppReOpenedListener appReopenHandler;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppForegroundHandler(final Runnable handler) {
 | 
			
		||||
        application.addAppEventListener( new AppForegroundListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
 | 
			
		||||
            }
 | 
			
		||||
        if (appForegroundHandler == null)
 | 
			
		||||
            application.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
 | 
			
		||||
                handler.run();
 | 
			
		||||
            }
 | 
			
		||||
        } );
 | 
			
		||||
                @Override
 | 
			
		||||
                public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
 | 
			
		||||
                    handler.run();
 | 
			
		||||
                }
 | 
			
		||||
            } );
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppForegroundHandler() {
 | 
			
		||||
        if (appForegroundHandler == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        application.removeAppEventListener( appForegroundHandler );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppReopenHandler(final Runnable handler) {
 | 
			
		||||
        application.addAppEventListener( (AppReOpenedListener) e -> handler.run() );
 | 
			
		||||
        application.addAppEventListener( appReopenHandler = e -> handler.run() );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppReopenHandler() {
 | 
			
		||||
        if (appReopenHandler == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        application.removeAppEventListener( appReopenHandler );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,21 @@ public class BasePlatform implements IPlatform {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppForegroundHandler() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppReopenHandler(final Runnable handler) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppReopenHandler() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean requestForeground() {
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,12 @@ public interface IPlatform {
 | 
			
		||||
 | 
			
		||||
    boolean installAppForegroundHandler(Runnable handler);
 | 
			
		||||
 | 
			
		||||
    boolean removeAppForegroundHandler();
 | 
			
		||||
 | 
			
		||||
    boolean installAppReopenHandler(Runnable handler);
 | 
			
		||||
 | 
			
		||||
    boolean removeAppReopenHandler();
 | 
			
		||||
 | 
			
		||||
    boolean requestForeground();
 | 
			
		||||
 | 
			
		||||
    boolean show(File file);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,24 +17,49 @@ public class JDK9Platform implements IPlatform {
 | 
			
		||||
    private static final Logger  logger  = Logger.get( JDK9Platform.class );
 | 
			
		||||
    private static final Desktop desktop = Desktop.getDesktop();
 | 
			
		||||
 | 
			
		||||
    private AppForegroundListener appForegroundHandler;
 | 
			
		||||
    private AppReopenedListener   appReopenHandler;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppForegroundHandler(final Runnable handler) {
 | 
			
		||||
        desktop.addAppEventListener( new AppForegroundListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void appRaisedToForeground(final AppForegroundEvent e) {
 | 
			
		||||
                handler.run();
 | 
			
		||||
            }
 | 
			
		||||
        if (appForegroundHandler == null)
 | 
			
		||||
            desktop.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void appRaisedToForeground(final AppForegroundEvent e) {
 | 
			
		||||
                    handler.run();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void appMovedToBackground(final AppForegroundEvent e) {
 | 
			
		||||
            }
 | 
			
		||||
        } );
 | 
			
		||||
                @Override
 | 
			
		||||
                public void appMovedToBackground(final AppForegroundEvent e) {
 | 
			
		||||
                }
 | 
			
		||||
            } );
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppForegroundHandler() {
 | 
			
		||||
        if (appForegroundHandler == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        desktop.removeAppEventListener( appForegroundHandler );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppReopenHandler(final Runnable handler) {
 | 
			
		||||
        desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
 | 
			
		||||
        if (appReopenHandler == null)
 | 
			
		||||
            desktop.addAppEventListener( appReopenHandler = e -> handler.run() );
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean removeAppReopenHandler() {
 | 
			
		||||
        if (appReopenHandler == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        desktop.removeAppEventListener( appReopenHandler );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.view;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.masterpassword.gui.MPGuiConfig;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Components;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Res;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.event.ComponentAdapter;
 | 
			
		||||
import java.awt.event.ComponentEvent;
 | 
			
		||||
import java.awt.event.*;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.border.BevelBorder;
 | 
			
		||||
 | 
			
		||||
@@ -19,7 +19,7 @@ public class MasterPasswordFrame extends JFrame {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = Logger.get( MasterPasswordFrame.class );
 | 
			
		||||
 | 
			
		||||
    private final UserContentPanel         userContent;
 | 
			
		||||
    private final UserContentPanel userContent;
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("MagicNumber")
 | 
			
		||||
    public MasterPasswordFrame() {
 | 
			
		||||
@@ -39,6 +39,7 @@ public class MasterPasswordFrame extends JFrame {
 | 
			
		||||
        userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
 | 
			
		||||
 | 
			
		||||
        addComponentListener( new ComponentHandler() );
 | 
			
		||||
        addWindowListener( new WindowHandler() );
 | 
			
		||||
        setPreferredSize( new Dimension( 800, 560 ) );
 | 
			
		||||
        setDefaultCloseOperation( DISPOSE_ON_CLOSE );
 | 
			
		||||
        pack();
 | 
			
		||||
@@ -55,4 +56,14 @@ public class MasterPasswordFrame extends JFrame {
 | 
			
		||||
            userContent.transferFocus();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static class WindowHandler extends WindowAdapter {
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void windowClosed(final WindowEvent e) {
 | 
			
		||||
            if (!MPGuiConfig.get().stayResident())
 | 
			
		||||
                System.exit( 0 );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,7 @@ import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import com.lyndir.masterpassword.gui.MPGuiConstants;
 | 
			
		||||
import com.lyndir.masterpassword.gui.MasterPassword;
 | 
			
		||||
import com.lyndir.masterpassword.gui.*;
 | 
			
		||||
import com.lyndir.masterpassword.gui.model.*;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.*;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Platform;
 | 
			
		||||
@@ -577,21 +576,25 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
 | 
			
		||||
            components.add( Components.label( "Default Algorithm:" ),
 | 
			
		||||
                            Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
 | 
			
		||||
                                                 user.getAlgorithm().version(),
 | 
			
		||||
                                                 version -> user.setAlgorithm( version.getAlgorithm() ) ) );
 | 
			
		||||
                                                 user.getAlgorithm().version(), version -> user.setAlgorithm( version.getAlgorithm() ) ) );
 | 
			
		||||
 | 
			
		||||
            MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
 | 
			
		||||
            if (fileUser != null) {
 | 
			
		||||
                components.add( Components.label( "Default Password Type:" ),
 | 
			
		||||
                                Components.comboBox( MPResultType.values(), MPResultType::getLongName,
 | 
			
		||||
                                                     fileUser.getPreferences().getDefaultType(),
 | 
			
		||||
                                                     fileUser.getPreferences()::setDefaultType ),
 | 
			
		||||
                                Components.strut() );
 | 
			
		||||
            components.add( Components.label( "Default Password Type:" ),
 | 
			
		||||
                            Components.comboBox( MPResultType.values(), MPResultType::getLongName,
 | 
			
		||||
                                                 user.getPreferences().getDefaultType(), user.getPreferences()::setDefaultType ),
 | 
			
		||||
                            Components.strut() );
 | 
			
		||||
 | 
			
		||||
                components.add( Components.checkBox( "Hide Passwords",
 | 
			
		||||
                                                     fileUser.getPreferences().isHidePasswords(),
 | 
			
		||||
                                                     fileUser.getPreferences()::setHidePasswords ) );
 | 
			
		||||
            }
 | 
			
		||||
            components.add( Components.checkBox( "Hide Passwords",
 | 
			
		||||
                                                 user.getPreferences().isHidePasswords(), user.getPreferences()::setHidePasswords ) );
 | 
			
		||||
 | 
			
		||||
            components.add( new JSeparator() );
 | 
			
		||||
 | 
			
		||||
            components.add( Components.checkBox( "Check For Updates",
 | 
			
		||||
                                                 MPGuiConfig.get().checkForUpdates(), MPGuiConfig.get()::setCheckForUpdates ) );
 | 
			
		||||
 | 
			
		||||
            components.add( Components.checkBox( strf( "<html>Stay Resident (reactivate with <strong><code>%s+%s</code></strong>)",
 | 
			
		||||
                                                       InputEvent.getModifiersExText( MPGuiConstants.ui_hotkey.getModifiers() ),
 | 
			
		||||
                                                       KeyEvent.getKeyText( MPGuiConstants.ui_hotkey.getKeyCode() ) ),
 | 
			
		||||
                                                 MPGuiConfig.get().stayResident(), MPGuiConfig.get()::setStayResident ) );
 | 
			
		||||
 | 
			
		||||
            Components.showDialog( this, user.getFullName(), new JOptionPane( Components.panel(
 | 
			
		||||
                    BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,78 @@
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import com.google.common.collect.ClassToInstanceMap;
 | 
			
		||||
import com.google.common.collect.MutableClassToInstanceMap;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.Changeable;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPJSONAnyObject;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2018-10-14
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("CallToSystemGetenv")
 | 
			
		||||
public class MPConfig extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
    private static final Logger                       logger     = Logger.get( MPConfig.class );
 | 
			
		||||
    private static final ClassToInstanceMap<MPConfig> instances  = MutableClassToInstanceMap.create();
 | 
			
		||||
    private static final File                         configFile = new File( rcDir(), "config.json" );
 | 
			
		||||
 | 
			
		||||
    private final        Changeable                   changeable = new Changeable() {
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void onChanged() {
 | 
			
		||||
            try {
 | 
			
		||||
                objectMapper.writerWithDefaultPrettyPrinter().writeValue( configFile, MPConfig.this );
 | 
			
		||||
                instances.clear();
 | 
			
		||||
            }
 | 
			
		||||
            catch (final IOException e) {
 | 
			
		||||
                logger.err( e, "While saving config to: %s", configFile );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    protected static synchronized <C extends MPConfig> C get(final Class<C> type) {
 | 
			
		||||
        C instance = instances.getInstance( type );
 | 
			
		||||
 | 
			
		||||
        if (instance == null)
 | 
			
		||||
            if (configFile.exists())
 | 
			
		||||
                try {
 | 
			
		||||
                    instances.putInstance( type, instance = objectMapper.readValue( configFile, type ) );
 | 
			
		||||
                }
 | 
			
		||||
                catch (final IOException e) {
 | 
			
		||||
                    logger.wrn( e, "While reading config file: %s", configFile );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if (instance == null)
 | 
			
		||||
            try {
 | 
			
		||||
                instance = type.getConstructor().newInstance();
 | 
			
		||||
            }
 | 
			
		||||
            catch (final ReflectiveOperationException e) {
 | 
			
		||||
                throw logger.bug( e );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        return instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void setChanged() {
 | 
			
		||||
        changeable.setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static MPConfig get() {
 | 
			
		||||
        return get( MPConfig.class );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static File rcDir() {
 | 
			
		||||
        String rcDir = System.getenv( MPModelConstants.env_rcDir );
 | 
			
		||||
        if (rcDir != null)
 | 
			
		||||
            return new File( rcDir );
 | 
			
		||||
 | 
			
		||||
        String home = System.getProperty( "user.home" );
 | 
			
		||||
        if (home == null)
 | 
			
		||||
            home = System.getenv( "HOME" );
 | 
			
		||||
 | 
			
		||||
        return new File( home, ".mpw.d" );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -40,14 +40,14 @@ public interface MPQuestion extends Comparable<MPQuestion> {
 | 
			
		||||
 | 
			
		||||
    void setType(MPResultType type);
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default String getAnswer()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getAnswer( null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Nullable
 | 
			
		||||
    String getAnswer(@Nullable String state)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import java.util.concurrent.Executors;
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2018-07-08
 | 
			
		||||
 */
 | 
			
		||||
public class Changeable {
 | 
			
		||||
public abstract class Changeable {
 | 
			
		||||
 | 
			
		||||
    private static final ExecutorService changeExecutor = Executors.newSingleThreadExecutor();
 | 
			
		||||
 | 
			
		||||
@@ -15,7 +15,9 @@ public class Changeable {
 | 
			
		||||
    private       Grouping grouping = Grouping.APPLY;
 | 
			
		||||
    private       boolean  changed;
 | 
			
		||||
 | 
			
		||||
    void setChanged() {
 | 
			
		||||
    protected abstract void onChanged();
 | 
			
		||||
 | 
			
		||||
    public void setChanged() {
 | 
			
		||||
        synchronized (mutex) {
 | 
			
		||||
            if (changed)
 | 
			
		||||
                return;
 | 
			
		||||
@@ -37,9 +39,6 @@ public class Changeable {
 | 
			
		||||
        } );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void beginChanges() {
 | 
			
		||||
        synchronized (mutex) {
 | 
			
		||||
            grouping = Grouping.BATCH;
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getAnswer(@Nullable final String state)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
@@ -82,8 +82,6 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
        super.onChanged();
 | 
			
		||||
 | 
			
		||||
        if (site instanceof Changeable)
 | 
			
		||||
            ((Changeable) site).setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -201,8 +201,6 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
        super.onChanged();
 | 
			
		||||
 | 
			
		||||
        if (user instanceof Changeable)
 | 
			
		||||
            ((Changeable) user).setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -214,8 +214,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
        super.onChanged();
 | 
			
		||||
 | 
			
		||||
        for (final Listener listener : listeners)
 | 
			
		||||
            listener.onUserUpdated( this );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ package com.lyndir.masterpassword.model.impl;
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +32,7 @@ public class MPFileQuestion extends MPBasicQuestion {
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private String answerState;
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("TypeMayBeWeakened")
 | 
			
		||||
    public MPFileQuestion(final MPFileSite site, final String keyword,
 | 
			
		||||
                          @Nullable final MPResultType type, @Nullable final String answerState) {
 | 
			
		||||
        super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
 | 
			
		||||
@@ -45,7 +45,7 @@ public class MPFileQuestion extends MPBasicQuestion {
 | 
			
		||||
        return answerState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getAnswer()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,9 @@
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model.impl;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableSortedSet;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPModelConstants;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPConfig;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
@@ -38,19 +36,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
 | 
			
		||||
public class MPFileUserManager {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("UnusedDeclaration")
 | 
			
		||||
    private static final Logger            logger = Logger.get( MPFileUserManager.class );
 | 
			
		||||
    private static final MPFileUserManager instance;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        String rcDir = System.getenv( MPModelConstants.env_rcDir );
 | 
			
		||||
 | 
			
		||||
        if (rcDir != null)
 | 
			
		||||
            instance = create( new File( rcDir ) );
 | 
			
		||||
        else {
 | 
			
		||||
            String home = ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) );
 | 
			
		||||
            instance = create( new File( home, ".mpw.d" ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private static final Logger            logger   = Logger.get( MPFileUserManager.class );
 | 
			
		||||
    private static final MPFileUserManager instance = create( MPConfig.get().rcDir() );
 | 
			
		||||
 | 
			
		||||
    private final Collection<Listener>    listeners  = new CopyOnWriteArraySet<>();
 | 
			
		||||
    private final Map<String, MPFileUser> userByName = new HashMap<>();
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@
 | 
			
		||||
package com.lyndir.masterpassword.model.impl;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.*;
 | 
			
		||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
 | 
			
		||||
import com.fasterxml.jackson.core.util.Separators;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
@@ -27,31 +30,59 @@ import java.util.*;
 | 
			
		||||
 * @author lhunath, 2018-05-14
 | 
			
		||||
 */
 | 
			
		||||
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MPJSONAnyObject.MPJSONEmptyValue.class)
 | 
			
		||||
class MPJSONAnyObject {
 | 
			
		||||
public class MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("serial")
 | 
			
		||||
    protected static final ObjectMapper objectMapper = new ObjectMapper() {
 | 
			
		||||
        {
 | 
			
		||||
            setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public DefaultPrettyPrinter withSeparators(final Separators separators) {
 | 
			
		||||
                    super.withSeparators( separators );
 | 
			
		||||
                    _objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
 | 
			
		||||
                    return this;
 | 
			
		||||
                }
 | 
			
		||||
            } );
 | 
			
		||||
            setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE );
 | 
			
		||||
            setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    @JsonAnySetter
 | 
			
		||||
    final Map<String, Object> any = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
    @JsonAnyGetter
 | 
			
		||||
    public Map<String, Object> getAny() {
 | 
			
		||||
    public Map<String, Object> any() {
 | 
			
		||||
        return Collections.unmodifiableMap( any );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public <V> V any(final String key) {
 | 
			
		||||
        return (V) any.get( key );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("EqualsAndHashcode")
 | 
			
		||||
    public static class MPJSONEmptyValue {
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings({ "ChainOfInstanceofChecks", "Contract" })
 | 
			
		||||
        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
 | 
			
		||||
        @SuppressFBWarnings({ "EQ_UNUSUAL", "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", "HE_EQUALS_USE_HASHCODE" })
 | 
			
		||||
        public boolean equals(final Object obj) {
 | 
			
		||||
            return isEmpty( obj );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @SuppressWarnings({ "ChainOfInstanceofChecks", "ConstantConditions" })
 | 
			
		||||
        private static boolean isEmpty(final Object obj) {
 | 
			
		||||
            if (obj == null)
 | 
			
		||||
                return true;
 | 
			
		||||
            if (obj instanceof Collection<?>)
 | 
			
		||||
                return ((Collection<?>) obj).isEmpty();
 | 
			
		||||
            if (obj instanceof Map<?, ?>)
 | 
			
		||||
                return ((Map<?, ?>) obj).isEmpty();
 | 
			
		||||
            if (obj instanceof MPJSONFile.Site.Ext)
 | 
			
		||||
                return ((MPJSONAnyObject) obj).any.isEmpty();
 | 
			
		||||
            if (obj instanceof MPJSONAnyObject)
 | 
			
		||||
                return ((MPJSONAnyObject) obj).any.isEmpty() && (objectMapper.valueToTree( obj ).size() == 0);
 | 
			
		||||
 | 
			
		||||
            return obj == null;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,22 +44,6 @@ import org.joda.time.Instant;
 | 
			
		||||
@SuppressFBWarnings("URF_UNREAD_FIELD")
 | 
			
		||||
public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
    protected static final ObjectMapper objectMapper = new ObjectMapper();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        objectMapper.setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
 | 
			
		||||
            private static final long serialVersionUID = 1;
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public DefaultPrettyPrinter withSeparators(final Separators separators) {
 | 
			
		||||
                super.withSeparators( separators );
 | 
			
		||||
                _objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
        } );
 | 
			
		||||
        objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MPJSONFile() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -79,8 +63,12 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
        user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
 | 
			
		||||
        user.key_id = modelUser.exportKeyID();
 | 
			
		||||
        user.algorithm = modelUser.getAlgorithm().version();
 | 
			
		||||
        user.default_type = modelUser.getPreferences().getDefaultType();
 | 
			
		||||
        user.hide_passwords = modelUser.getPreferences().isHidePasswords();
 | 
			
		||||
        user._ext_mpw = new User.Ext() {
 | 
			
		||||
            {
 | 
			
		||||
                default_type = modelUser.getPreferences().getDefaultType();
 | 
			
		||||
                hide_passwords = modelUser.getPreferences().isHidePasswords();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Section "sites"
 | 
			
		||||
        sites = new LinkedHashMap<>();
 | 
			
		||||
@@ -131,8 +119,11 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
                    }
 | 
			
		||||
                } );
 | 
			
		||||
 | 
			
		||||
            site._ext_mpw = new Site.Ext();
 | 
			
		||||
            site._ext_mpw.url = modelSite.getUrl();
 | 
			
		||||
            site._ext_mpw = new Site.Ext() {
 | 
			
		||||
                {
 | 
			
		||||
                    url = modelSite.getUrl();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -141,9 +132,10 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
        return new MPFileUser(
 | 
			
		||||
                user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
 | 
			
		||||
                (user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
 | 
			
		||||
                (user._ext_mpw != null)? user._ext_mpw.default_type: null,
 | 
			
		||||
                (user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
 | 
			
		||||
                user.hide_passwords, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
 | 
			
		||||
                (user._ext_mpw != null) && user._ext_mpw.hide_passwords,
 | 
			
		||||
                export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
 | 
			
		||||
                MPMarshalFormat.JSON, file
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -203,16 +195,24 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
    public static class User extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
        int     avatar;
 | 
			
		||||
        String  full_name;
 | 
			
		||||
        String  last_used;
 | 
			
		||||
        boolean hide_passwords;
 | 
			
		||||
        int    avatar;
 | 
			
		||||
        String full_name;
 | 
			
		||||
        String last_used;
 | 
			
		||||
        @Nullable
 | 
			
		||||
        String              key_id;
 | 
			
		||||
        @Nullable
 | 
			
		||||
        MPAlgorithm.Version algorithm;
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
        MPResultType        default_type;
 | 
			
		||||
        Ext _ext_mpw;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public static class Ext extends MPJSONAnyObject {
 | 
			
		||||
 | 
			
		||||
            @Nullable
 | 
			
		||||
            MPResultType default_type;
 | 
			
		||||
            boolean hide_passwords;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user