Site settings & add sites.
This commit is contained in:
		@@ -6,7 +6,7 @@ description = 'Master Password Algorithm Implementation'
 | 
			
		||||
 | 
			
		||||
tasks.withType( JavaCompile ) {
 | 
			
		||||
    // Native headers
 | 
			
		||||
    options.compilerArgs += ["-h", new File( new File( project( ':masterpassword-core' ).projectDir, 'src' ), 'java' ).absolutePath]
 | 
			
		||||
    options.compilerArgs += ["-h", new File( new File( project.project( ':masterpassword-core' ).projectDir, 'src' ), 'java' ).absolutePath]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package com.lyndir.masterpassword.util;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +19,14 @@ public final class Utilities {
 | 
			
		||||
        return consumer.apply( value );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
 | 
			
		||||
        if (value == null)
 | 
			
		||||
            return nullValue;
 | 
			
		||||
 | 
			
		||||
        return consumer.apply( value );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T> void ifNotNullDo(@Nullable final T value, final Consumer<T> consumer) {
 | 
			
		||||
        if (value != null)
 | 
			
		||||
            consumer.accept( value );
 | 
			
		||||
 
 | 
			
		||||
@@ -47,8 +47,8 @@ public class GUI {
 | 
			
		||||
    private final MasterPasswordFrame frame = new MasterPasswordFrame();
 | 
			
		||||
 | 
			
		||||
    public static void main(final String... args) {
 | 
			
		||||
        Thread.setDefaultUncaughtExceptionHandler(
 | 
			
		||||
                (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
 | 
			
		||||
//        Thread.setDefaultUncaughtExceptionHandler(
 | 
			
		||||
//                (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
 | 
			
		||||
 | 
			
		||||
        if (Config.get().checkForUpdates())
 | 
			
		||||
            checkUpdate();
 | 
			
		||||
 
 | 
			
		||||
@@ -29,25 +29,15 @@ import javax.annotation.Nullable;
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-16
 | 
			
		||||
 */
 | 
			
		||||
public class MPIncognitoSite extends MPBasicSite<MPIncognitoQuestion> {
 | 
			
		||||
public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQuestion> {
 | 
			
		||||
 | 
			
		||||
    private final MPIncognitoUser user;
 | 
			
		||||
 | 
			
		||||
    public MPIncognitoSite(final MPIncognitoUser user, final String name) {
 | 
			
		||||
        this( user, name, null, null, null, null );
 | 
			
		||||
    public MPIncognitoSite(final MPIncognitoUser user, final String siteName) {
 | 
			
		||||
        this( user, siteName, null, null, null, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPIncognitoSite(final MPIncognitoUser user, final String name,
 | 
			
		||||
    public MPIncognitoSite(final MPIncognitoUser user, final String siteName,
 | 
			
		||||
                           @Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
 | 
			
		||||
                           @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
 | 
			
		||||
        super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
 | 
			
		||||
 | 
			
		||||
        this.user = user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPIncognitoUser getUser() {
 | 
			
		||||
        return user;
 | 
			
		||||
        super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,4 +37,9 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
 | 
			
		||||
    public byte[] getKeyID() {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPIncognitoSite addSite(final String siteName) {
 | 
			
		||||
        return addSite( new MPIncognitoSite( this, siteName ) );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.model;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.masterpassword.model.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.*;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2018-07-27
 | 
			
		||||
 */
 | 
			
		||||
public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
 | 
			
		||||
 | 
			
		||||
    public MPNewSite(final MPUser<?> user, final String siteName) {
 | 
			
		||||
        super( user, siteName, user.getAlgorithm() );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,8 @@ import javax.swing.event.ListSelectionListener;
 | 
			
		||||
 * @author lhunath, 2018-07-19
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("serial")
 | 
			
		||||
public class CollectionListModel<E> extends AbstractListModel<E> implements ComboBoxModel<E>, ListSelectionListener {
 | 
			
		||||
public class CollectionListModel<E> extends AbstractListModel<E>
 | 
			
		||||
        implements ComboBoxModel<E>, ListSelectionListener, Selectable<E, CollectionListModel<E>> {
 | 
			
		||||
 | 
			
		||||
    private final List<E>     model = new LinkedList<>();
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -55,13 +56,16 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
 | 
			
		||||
     * This operation will mutate the internal model to reflect the given model.
 | 
			
		||||
     * The given model will remain untouched and independent from this object.
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("AssignmentToForLoopParameter")
 | 
			
		||||
    public synchronized void set(final Collection<? extends E> newModel) {
 | 
			
		||||
        ImmutableList<? extends E> newModelList = ImmutableList.copyOf( newModel );
 | 
			
		||||
    @SuppressWarnings({ "unchecked", "SuspiciousToArrayCall" })
 | 
			
		||||
    public synchronized void set(final Collection<? extends E> elements) {
 | 
			
		||||
        set( (E[]) elements.toArray( new Object[0] ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("AssignmentToForLoopParameter")
 | 
			
		||||
    public synchronized void set(final E... elements) {
 | 
			
		||||
        ListIterator<E> oldIt = model.listIterator();
 | 
			
		||||
        for (int from = 0; oldIt.hasNext(); ++from) {
 | 
			
		||||
            int to = newModelList.indexOf( oldIt.next() );
 | 
			
		||||
            int to = Arrays.binarySearch( elements, oldIt.next() );
 | 
			
		||||
 | 
			
		||||
            if (to != from) {
 | 
			
		||||
                oldIt.remove();
 | 
			
		||||
@@ -70,9 +74,8 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Iterator<? extends E> newIt = newModelList.iterator();
 | 
			
		||||
        for (int to = 0; newIt.hasNext(); ++to) {
 | 
			
		||||
            E newSite = newIt.next();
 | 
			
		||||
        for (int to = 0; to < elements.length; ++to) {
 | 
			
		||||
            E newSite = elements[to];
 | 
			
		||||
 | 
			
		||||
            if ((to >= model.size()) || !Objects.equals( model.get( to ), newSite )) {
 | 
			
		||||
                model.add( to, newSite );
 | 
			
		||||
@@ -116,6 +119,7 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
 | 
			
		||||
        this.list.setModel( this );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized CollectionListModel<E> selection(@Nullable final Consumer<E> selectionConsumer) {
 | 
			
		||||
        this.selectionConsumer = selectionConsumer;
 | 
			
		||||
        if (selectionConsumer != null)
 | 
			
		||||
@@ -124,6 +128,7 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
 | 
			
		||||
        this.selectionConsumer = null;
 | 
			
		||||
        setSelectedItem( selectedItem );
 | 
			
		||||
@@ -134,7 +139,11 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void valueChanged(final ListSelectionEvent event) {
 | 
			
		||||
        //noinspection ObjectEquality
 | 
			
		||||
        if ((event.getSource() == list) && (list.getModel() == this))
 | 
			
		||||
        if (!event.getValueIsAdjusting() && (event.getSource() == list) && (list.getModel() == this)) {
 | 
			
		||||
            selectedItem = list.getSelectedValue();
 | 
			
		||||
 | 
			
		||||
            if (selectionConsumer != null)
 | 
			
		||||
                selectionConsumer.accept( selectedItem );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ import javax.annotation.Nullable;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.border.Border;
 | 
			
		||||
import javax.swing.border.CompoundBorder;
 | 
			
		||||
import javax.swing.event.DocumentEvent;
 | 
			
		||||
import javax.swing.event.DocumentListener;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -41,6 +43,12 @@ public abstract class Components {
 | 
			
		||||
    public static final int   SIZE_MARGIN       = 12;
 | 
			
		||||
    public static final int   SIZE_PADDING      = 8;
 | 
			
		||||
 | 
			
		||||
    public static GradientPanel panel(final Component... components) {
 | 
			
		||||
        GradientPanel panel = panel( BoxLayout.LINE_AXIS, null, components );
 | 
			
		||||
        panel.setLayout( new OverlayLayout( panel ) );
 | 
			
		||||
        return panel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static GradientPanel panel(final int axis, final Component... components) {
 | 
			
		||||
        return panel( axis, null, components );
 | 
			
		||||
    }
 | 
			
		||||
@@ -124,6 +132,40 @@ public abstract class Components {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> selection) {
 | 
			
		||||
        return new JTextField( text ) {
 | 
			
		||||
            {
 | 
			
		||||
                setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
 | 
			
		||||
                                                               BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
 | 
			
		||||
                setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
 | 
			
		||||
                setAlignmentX( LEFT_ALIGNMENT );
 | 
			
		||||
 | 
			
		||||
                if (selection != null)
 | 
			
		||||
                    getDocument().addDocumentListener( new DocumentListener() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void insertUpdate(final DocumentEvent e) {
 | 
			
		||||
                            selection.accept( getText() );
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void removeUpdate(final DocumentEvent e) {
 | 
			
		||||
                            selection.accept( getText() );
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void changedUpdate(final DocumentEvent e) {
 | 
			
		||||
                            selection.accept( getText() );
 | 
			
		||||
                        }
 | 
			
		||||
                    } );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public Dimension getMaximumSize() {
 | 
			
		||||
                return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static JPasswordField passwordField() {
 | 
			
		||||
        return new JPasswordField() {
 | 
			
		||||
            {
 | 
			
		||||
@@ -143,18 +185,16 @@ public abstract class Components {
 | 
			
		||||
        return new JList<E>( model ) {
 | 
			
		||||
            {
 | 
			
		||||
                setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
 | 
			
		||||
                setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
 | 
			
		||||
                setCellRenderer( new DefaultListCellRenderer() {
 | 
			
		||||
                    {
 | 
			
		||||
                        setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
 | 
			
		||||
                    public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
 | 
			
		||||
                                                                  final boolean isSelected, final boolean cellHasFocus) {
 | 
			
		||||
                        return super.getListCellRendererComponent(
 | 
			
		||||
                        super.getListCellRendererComponent(
 | 
			
		||||
                                list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus );
 | 
			
		||||
                        setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) );
 | 
			
		||||
 | 
			
		||||
                        return this;
 | 
			
		||||
                    }
 | 
			
		||||
                } );
 | 
			
		||||
                setAlignmentX( LEFT_ALIGNMENT );
 | 
			
		||||
@@ -248,11 +288,6 @@ public abstract class Components {
 | 
			
		||||
                setAlignmentX( LEFT_ALIGNMENT );
 | 
			
		||||
                setBorder( null );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public Dimension getMaximumSize() {
 | 
			
		||||
                return new Dimension( 20, getPreferredSize().height );
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -362,16 +397,15 @@ public abstract class Components {
 | 
			
		||||
                setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
 | 
			
		||||
                setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
 | 
			
		||||
                setRenderer( new DefaultListCellRenderer() {
 | 
			
		||||
                    {
 | 
			
		||||
                        setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    @Override
 | 
			
		||||
                    @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
 | 
			
		||||
                    public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
 | 
			
		||||
                                                                  final boolean isSelected, final boolean cellHasFocus) {
 | 
			
		||||
                        return super.getListCellRendererComponent(
 | 
			
		||||
                        super.getListCellRendererComponent(
 | 
			
		||||
                                list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus );
 | 
			
		||||
                        setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
 | 
			
		||||
 | 
			
		||||
                        return this;
 | 
			
		||||
                    }
 | 
			
		||||
                } );
 | 
			
		||||
                putClientProperty( "JComboBox.isPopDown", Boolean.TRUE );
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ import java.util.concurrent.*;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import org.jetbrains.annotations.NonNls;
 | 
			
		||||
import org.joda.time.ReadableInstant;
 | 
			
		||||
import org.joda.time.format.DateTimeFormat;
 | 
			
		||||
import org.joda.time.format.DateTimeFormatter;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -103,6 +106,10 @@ public abstract class Res {
 | 
			
		||||
        return colors;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String format(final ReadableInstant instant) {
 | 
			
		||||
        return DateTimeFormat.mediumDateTime().print( instant );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final class Icons {
 | 
			
		||||
 | 
			
		||||
        public Icon add() {
 | 
			
		||||
@@ -121,6 +128,10 @@ public abstract class Res {
 | 
			
		||||
            return icon( "media/icon_user.png" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Icon settings() {
 | 
			
		||||
            return icon( "media/icon_settings.png" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Icon avatar(final int index) {
 | 
			
		||||
            return icon( strf( "media/avatar-%d.png", index % avatars() ) );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.util;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2018-07-26
 | 
			
		||||
 */
 | 
			
		||||
public interface Selectable<E, T> {
 | 
			
		||||
 | 
			
		||||
    T selection(@Nullable Consumer<E> selectionConsumer);
 | 
			
		||||
 | 
			
		||||
    T selection(E selectedItem, @Nullable Consumer<E> selectionConsumer);
 | 
			
		||||
}
 | 
			
		||||
@@ -19,16 +19,24 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.util;
 | 
			
		||||
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.event.ChangeEvent;
 | 
			
		||||
import javax.swing.event.ChangeListener;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2016-10-29
 | 
			
		||||
 */
 | 
			
		||||
public class UnsignedIntegerModel extends SpinnerNumberModel {
 | 
			
		||||
public class UnsignedIntegerModel extends SpinnerNumberModel
 | 
			
		||||
implements Selectable<UnsignedInteger, UnsignedIntegerModel> {
 | 
			
		||||
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private ChangeListener changeListener;
 | 
			
		||||
 | 
			
		||||
    public UnsignedIntegerModel() {
 | 
			
		||||
        this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
 | 
			
		||||
    }
 | 
			
		||||
@@ -55,4 +63,30 @@ public class UnsignedIntegerModel extends SpinnerNumberModel {
 | 
			
		||||
    public UnsignedInteger getNumber() {
 | 
			
		||||
        return (UnsignedInteger) super.getNumber();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public UnsignedIntegerModel selection(@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
 | 
			
		||||
        if (changeListener != null) {
 | 
			
		||||
            removeChangeListener( changeListener );
 | 
			
		||||
            changeListener = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (selectionConsumer != null) {
 | 
			
		||||
            addChangeListener( changeListener = e -> selectionConsumer.accept( getNumber() ) );
 | 
			
		||||
            selectionConsumer.accept( getNumber() );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public UnsignedIntegerModel selection(final UnsignedInteger selectedItem, @Nullable final Consumer<UnsignedInteger> selectionConsumer) {
 | 
			
		||||
        if (changeListener != null) {
 | 
			
		||||
            removeChangeListener( changeListener );
 | 
			
		||||
            changeListener = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setValue( selectedItem );
 | 
			
		||||
        return selection( selectionConsumer );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,6 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener,
 | 
			
		||||
        setDefaultCloseOperation( DISPOSE_ON_CLOSE );
 | 
			
		||||
        setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) );
 | 
			
		||||
        root.add( filesPanel );
 | 
			
		||||
        root.add( new JSeparator( SwingConstants.HORIZONTAL ) );
 | 
			
		||||
        root.add( Components.strut() );
 | 
			
		||||
        root.add( Components.borderPanel(
 | 
			
		||||
                BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,14 @@ package com.lyndir.masterpassword.gui.view;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableCollection;
 | 
			
		||||
import com.google.common.base.Joiner;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Res;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.CollectionListModel;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Components;
 | 
			
		||||
import com.lyndir.masterpassword.gui.model.MPNewSite;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPFileSite;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
 | 
			
		||||
@@ -17,15 +17,15 @@ import java.awt.*;
 | 
			
		||||
import java.awt.datatransfer.StringSelection;
 | 
			
		||||
import java.awt.datatransfer.Transferable;
 | 
			
		||||
import java.awt.event.*;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.event.*;
 | 
			
		||||
import javax.swing.event.DocumentEvent;
 | 
			
		||||
import javax.swing.event.DocumentListener;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -166,7 +166,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private synchronized void update() {
 | 
			
		||||
            errorLabel.setText( null );
 | 
			
		||||
            errorLabel.setText( " " );
 | 
			
		||||
 | 
			
		||||
            if (identiconJob != null)
 | 
			
		||||
                identiconJob.cancel( true );
 | 
			
		||||
@@ -193,38 +193,47 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static final class AuthenticatedUserPanel extends JPanel implements ActionListener, DocumentListener, ListSelectionListener,
 | 
			
		||||
            KeyListener {
 | 
			
		||||
    private static final class AuthenticatedUserPanel extends JPanel implements KeyListener {
 | 
			
		||||
 | 
			
		||||
        public static final int SIZE_RESULT = 48;
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        private final MPUser<?>                      user;
 | 
			
		||||
        private final JLabel                         passwordLabel = Components.label( SwingConstants.CENTER );
 | 
			
		||||
        private final JLabel                         passwordField = Components.heading( SwingConstants.CENTER );
 | 
			
		||||
        private final JLabel                         queryLabel    = Components.label();
 | 
			
		||||
        private final JTextField                     queryField    = Components.textField();
 | 
			
		||||
        private final CollectionListModel<MPSite<?>> sitesModel    = new CollectionListModel<>();
 | 
			
		||||
        private final JList<MPSite<?>>               sitesList     = Components.list( sitesModel,
 | 
			
		||||
                                                                                      value -> (value != null)? value.getName(): null );
 | 
			
		||||
        private       Future<?>                      updateSitesJob;
 | 
			
		||||
        private final JLabel                         passwordLabel  = Components.label( SwingConstants.CENTER );
 | 
			
		||||
        private final JLabel                         passwordField  = Components.heading( SwingConstants.CENTER );
 | 
			
		||||
        private final JButton                        passwordButton =
 | 
			
		||||
                Components.button( Res.icons().settings(), event -> showSiteSettings() );
 | 
			
		||||
        private final JLabel                         queryLabel     = Components.label();
 | 
			
		||||
        private final JTextField                     queryField     = Components.textField( null, this::updateSites );
 | 
			
		||||
        private final CollectionListModel<MPSite<?>> sitesModel     =
 | 
			
		||||
                new CollectionListModel<MPSite<?>>().selection( this::showSiteResult );
 | 
			
		||||
        private final JList<MPSite<?>>               sitesList      =
 | 
			
		||||
                Components.list( sitesModel, this::getSiteDescription );
 | 
			
		||||
 | 
			
		||||
        private Future<?> updateSitesJob;
 | 
			
		||||
 | 
			
		||||
        private AuthenticatedUserPanel(@Nonnull final MPUser<?> user) {
 | 
			
		||||
            setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
 | 
			
		||||
 | 
			
		||||
            this.user = user;
 | 
			
		||||
 | 
			
		||||
            add( new Components.GradientPanel( new BorderLayout() ) {
 | 
			
		||||
                {
 | 
			
		||||
                    add( Components.heading( user.getFullName(), SwingConstants.CENTER ), BorderLayout.CENTER );
 | 
			
		||||
                    add( Components.button( Res.icons().user(), event -> showPreferences() ), BorderLayout.LINE_END );
 | 
			
		||||
                }
 | 
			
		||||
            } );
 | 
			
		||||
            add( Components.panel(
 | 
			
		||||
                    Components.heading( user.getFullName(), SwingConstants.CENTER ),
 | 
			
		||||
                    Components.panel(
 | 
			
		||||
                            BoxLayout.LINE_AXIS,
 | 
			
		||||
                            Box.createGlue(),
 | 
			
		||||
                            Components.button( Res.icons().user(), event -> showUserPreferences() ) ) ) );
 | 
			
		||||
 | 
			
		||||
            add( passwordLabel );
 | 
			
		||||
            add( passwordField );
 | 
			
		||||
            add( Components.panel(
 | 
			
		||||
                    passwordField,
 | 
			
		||||
                    Components.panel(
 | 
			
		||||
                            BoxLayout.LINE_AXIS,
 | 
			
		||||
                            Box.createGlue(),
 | 
			
		||||
                            passwordButton ) ) );
 | 
			
		||||
            passwordField.setForeground( Res.colors().highlightFg() );
 | 
			
		||||
            passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
 | 
			
		||||
            passwordButton.setVisible( false );
 | 
			
		||||
            add( Box.createGlue() );
 | 
			
		||||
            add( Components.strut() );
 | 
			
		||||
 | 
			
		||||
@@ -232,18 +241,16 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
            queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
 | 
			
		||||
            add( queryField );
 | 
			
		||||
            queryField.putClientProperty( "JTextField.variant", "search" );
 | 
			
		||||
            queryField.addActionListener( this );
 | 
			
		||||
            queryField.addActionListener( event -> useSite() );
 | 
			
		||||
            queryField.addKeyListener( this );
 | 
			
		||||
            queryField.getDocument().addDocumentListener( this );
 | 
			
		||||
            queryField.requestFocusInWindow();
 | 
			
		||||
            add( Components.strut() );
 | 
			
		||||
            add( Components.scrollPane( sitesList ) );
 | 
			
		||||
            sitesModel.registerList( sitesList );
 | 
			
		||||
            sitesList.addListSelectionListener( this );
 | 
			
		||||
            add( Box.createGlue() );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void showPreferences() {
 | 
			
		||||
        public void showUserPreferences() {
 | 
			
		||||
            ImmutableList.Builder<Component> components = ImmutableList.builder();
 | 
			
		||||
 | 
			
		||||
            MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
 | 
			
		||||
@@ -262,9 +269,79 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
                    BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void actionPerformed(final ActionEvent event) {
 | 
			
		||||
            MPSite<?> site = sitesList.getSelectedValue();
 | 
			
		||||
        public void showSiteSettings() {
 | 
			
		||||
            ImmutableList.Builder<Component> components = ImmutableList.builder();
 | 
			
		||||
 | 
			
		||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
			
		||||
            if (site == null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            components.add( Components.label( "Algorithm:" ),
 | 
			
		||||
                            Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
 | 
			
		||||
                                                 site.getAlgorithm().version(),
 | 
			
		||||
                                                 version -> site.setAlgorithm( version.getAlgorithm() ) ) );
 | 
			
		||||
 | 
			
		||||
            components.add( Components.label( "Counter:" ),
 | 
			
		||||
                            Components.spinner( new UnsignedIntegerModel( site.getCounter(), UnsignedInteger.ONE )
 | 
			
		||||
                                                        .selection( site::setCounter ) ),
 | 
			
		||||
                            Components.strut() );
 | 
			
		||||
 | 
			
		||||
            components.add( Components.label( "Password Type:" ),
 | 
			
		||||
                            Components.comboBox( MPResultType.values(), MPResultType::getLongName,
 | 
			
		||||
                                                 site.getResultType(), site::setResultType ),
 | 
			
		||||
                            Components.strut() );
 | 
			
		||||
 | 
			
		||||
            components.add( Components.label( "Login Type:" ),
 | 
			
		||||
                            Components.comboBox( MPResultType.values(), MPResultType::getLongName,
 | 
			
		||||
                                                 site.getLoginType(), site::setLoginType ),
 | 
			
		||||
                            Components.strut() );
 | 
			
		||||
 | 
			
		||||
            MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null;
 | 
			
		||||
            if (fileSite != null)
 | 
			
		||||
                components.add( Components.label( "URL:" ),
 | 
			
		||||
                                Components.textField( fileSite.getUrl(), fileSite::setUrl ),
 | 
			
		||||
                                Components.strut() );
 | 
			
		||||
 | 
			
		||||
            Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
 | 
			
		||||
                    BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private String getSiteDescription(@Nonnull final MPSite<?> site) {
 | 
			
		||||
            if (site instanceof MPNewSite)
 | 
			
		||||
                return strf( "<html><strong>%s</strong> <Add new site></html>", queryField.getText() );
 | 
			
		||||
 | 
			
		||||
            ImmutableList.Builder<Object> parameters = ImmutableList.builder();
 | 
			
		||||
            try {
 | 
			
		||||
                MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null;
 | 
			
		||||
                if (fileSite != null)
 | 
			
		||||
                    parameters.add( Res.format( fileSite.getLastUsed() ) );
 | 
			
		||||
                parameters.add( site.getAlgorithm().version() );
 | 
			
		||||
                parameters.add( strf( "#%d", site.getCounter().longValue() ) );
 | 
			
		||||
                parameters.add( strf( "<em>%s</em>", site.getLogin() ) );
 | 
			
		||||
                if ((fileSite != null) && (fileSite.getUrl() != null))
 | 
			
		||||
                    parameters.add( fileSite.getUrl() );
 | 
			
		||||
            }
 | 
			
		||||
            catch (final MPAlgorithmException | MPKeyUnavailableException e) {
 | 
			
		||||
                logger.err( e, "While generating site description." );
 | 
			
		||||
                parameters.add( e.getLocalizedMessage() );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return strf( "<html><strong>%s</strong> (%s)</html>", site.getSiteName(),
 | 
			
		||||
                         Joiner.on( " - " ).skipNulls().join( parameters.build() ) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void useSite() {
 | 
			
		||||
            MPSite<?> site = sitesModel.getSelectedItem();
 | 
			
		||||
            if (site instanceof MPNewSite) {
 | 
			
		||||
                if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
 | 
			
		||||
                        this, strf( "<html>Remember the site [<strong>%s</strong>]?</html>", site.getSiteName() ),
 | 
			
		||||
                        "New Site", JOptionPane.YES_NO_OPTION )) {
 | 
			
		||||
                    sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) );
 | 
			
		||||
                    useSite();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            showSiteResult( site, result -> {
 | 
			
		||||
                if (result == null)
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -282,24 +359,8 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
            } );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void insertUpdate(final DocumentEvent event) {
 | 
			
		||||
            updateSites();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void removeUpdate(final DocumentEvent event) {
 | 
			
		||||
            updateSites();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void changedUpdate(final DocumentEvent event) {
 | 
			
		||||
            updateSites();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void valueChanged(final ListSelectionEvent event) {
 | 
			
		||||
            showSiteResult( event.getValueIsAdjusting()? null: sitesList.getSelectedValue(), null );
 | 
			
		||||
        private void showSiteResult(@Nullable final MPSite<?> site) {
 | 
			
		||||
            showSiteResult( site, null );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void showSiteResult(@Nullable final MPSite<?> site, @Nullable final Consumer<String> resultCallback) {
 | 
			
		||||
@@ -309,22 +370,21 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
                Res.ui( () -> {
 | 
			
		||||
                    passwordLabel.setText( " " );
 | 
			
		||||
                    passwordField.setText( " " );
 | 
			
		||||
                    passwordButton.setVisible( false );
 | 
			
		||||
                } );
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            String siteName = site.getName();
 | 
			
		||||
            Res.job( () -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    String result = user.getMasterKey().siteResult(
 | 
			
		||||
                            siteName, user.getAlgorithm(), UnsignedInteger.ONE,
 | 
			
		||||
                            MPKeyPurpose.Authentication, null, MPResultType.GeneratedLong, null );
 | 
			
		||||
                    String result = site.getResult();
 | 
			
		||||
                    if (resultCallback != null)
 | 
			
		||||
                        resultCallback.accept( result );
 | 
			
		||||
 | 
			
		||||
                    Res.ui( () -> {
 | 
			
		||||
                        passwordLabel.setText( strf( "Your password for %s:", siteName ) );
 | 
			
		||||
                        passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
 | 
			
		||||
                        passwordField.setText( result );
 | 
			
		||||
                        passwordButton.setVisible( true );
 | 
			
		||||
                    } );
 | 
			
		||||
                }
 | 
			
		||||
                catch (final MPKeyUnavailableException | MPAlgorithmException e) {
 | 
			
		||||
@@ -349,12 +409,19 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
 | 
			
		||||
                sitesList.dispatchEvent( event );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private synchronized void updateSites() {
 | 
			
		||||
        private synchronized void updateSites(@Nullable final String query) {
 | 
			
		||||
            if (updateSitesJob != null)
 | 
			
		||||
                updateSitesJob.cancel( true );
 | 
			
		||||
 | 
			
		||||
            updateSitesJob = Res.job( () -> {
 | 
			
		||||
                ImmutableCollection<? extends MPSite<?>> sites = user.findSites( queryField.getText() );
 | 
			
		||||
                Collection<MPSite<?>> sites = new LinkedList<>();
 | 
			
		||||
                if (!Strings.isNullOrEmpty( query )) {
 | 
			
		||||
                    sites.addAll( new LinkedList<>( user.findSites( query ) ) );
 | 
			
		||||
 | 
			
		||||
                    if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
 | 
			
		||||
                        sites.add( new MPNewSite( user, query ) );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Res.ui( () -> sitesModel.set( sites ) );
 | 
			
		||||
            } );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.5 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.5 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.3 KiB  | 
@@ -33,7 +33,7 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
 | 
			
		||||
    // - Meta
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    String getName();
 | 
			
		||||
    String getSiteName();
 | 
			
		||||
 | 
			
		||||
    // - Algorithm
 | 
			
		||||
 | 
			
		||||
@@ -57,10 +57,34 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
 | 
			
		||||
 | 
			
		||||
    void setLoginType(@Nullable MPResultType loginType);
 | 
			
		||||
 | 
			
		||||
    default String getResult()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getResult( MPKeyPurpose.Authentication );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    default String getResult(final MPKeyPurpose keyPurpose)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
        return getResult( keyPurpose, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
        return getResult( keyPurpose, keyContext, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException;
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    default String getLogin()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
        return getLogin( null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    String getLogin(@Nullable String state)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,6 @@ package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableCollection;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPBasicSite;
 | 
			
		||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -87,7 +85,10 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
 | 
			
		||||
 | 
			
		||||
    // - Relations
 | 
			
		||||
 | 
			
		||||
    void addSite(S site);
 | 
			
		||||
    S addSite(String siteName);
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    S addSite(S site);
 | 
			
		||||
 | 
			
		||||
    void deleteSite(S site);
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +96,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
 | 
			
		||||
    Collection<S> getSites();
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    ImmutableCollection<S> findSites(String query);
 | 
			
		||||
    ImmutableCollection<S> findSites(@Nullable String query);
 | 
			
		||||
 | 
			
		||||
    boolean addListener(Listener listener);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public abstract MPBasicSite<?> getSite();
 | 
			
		||||
    public abstract MPBasicSite<?, ?> getSite();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,34 +23,36 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPQuestion;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPSite;
 | 
			
		||||
import com.lyndir.masterpassword.model.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-16
 | 
			
		||||
 */
 | 
			
		||||
public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable implements MPSite<Q> {
 | 
			
		||||
public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable
 | 
			
		||||
        implements MPSite<Q> {
 | 
			
		||||
 | 
			
		||||
    private final Collection<Q> questions = new LinkedHashSet<>();
 | 
			
		||||
    private final U             user;
 | 
			
		||||
    private final String        siteName;
 | 
			
		||||
 | 
			
		||||
    private String          name;
 | 
			
		||||
    private MPAlgorithm     algorithm;
 | 
			
		||||
    private UnsignedInteger counter;
 | 
			
		||||
    private MPResultType    resultType;
 | 
			
		||||
    private MPResultType    loginType;
 | 
			
		||||
 | 
			
		||||
    private final Collection<Q> questions = new LinkedHashSet<>();
 | 
			
		||||
 | 
			
		||||
    protected MPBasicSite(final String name, final MPAlgorithm algorithm) {
 | 
			
		||||
        this( name, algorithm, null, null, null );
 | 
			
		||||
    protected MPBasicSite(final U user, final String siteName, final MPAlgorithm algorithm) {
 | 
			
		||||
        this( user, siteName, algorithm, null, null, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MPBasicSite(final String name, final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
 | 
			
		||||
    protected MPBasicSite(final U user, final String siteName, final MPAlgorithm algorithm,
 | 
			
		||||
                          @Nullable final UnsignedInteger counter,
 | 
			
		||||
                          @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.user = user;
 | 
			
		||||
        this.siteName = siteName;
 | 
			
		||||
        this.algorithm = algorithm;
 | 
			
		||||
        this.counter = (counter == null)? algorithm.mpw_default_counter(): counter;
 | 
			
		||||
        this.resultType = (resultType == null)? algorithm.mpw_default_result_type(): resultType;
 | 
			
		||||
@@ -59,8 +61,8 @@ public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable imple
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        return name;
 | 
			
		||||
    public String getSiteName() {
 | 
			
		||||
        return siteName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -128,7 +130,7 @@ public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable imple
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getUser().getMasterKey().siteResult(
 | 
			
		||||
                getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
 | 
			
		||||
                getSiteName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
 | 
			
		||||
                keyPurpose, keyContext, type, state );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -137,7 +139,7 @@ public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable imple
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getUser().getMasterKey().siteState(
 | 
			
		||||
                getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
 | 
			
		||||
                getSiteName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
 | 
			
		||||
                keyPurpose, keyContext, type, state );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -171,32 +173,35 @@ public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable imple
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public abstract MPBasicUser<?> getUser();
 | 
			
		||||
    public U getUser() {
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
        super.onChanged();
 | 
			
		||||
 | 
			
		||||
        getUser().setChanged();
 | 
			
		||||
        if (user instanceof Changeable)
 | 
			
		||||
            ((Changeable) user).setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hashCode( getName() );
 | 
			
		||||
        return Objects.hashCode( getSiteName() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(final Object obj) {
 | 
			
		||||
        return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getName(), ((MPSite<?>) obj).getName() ));
 | 
			
		||||
        return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite<?>) obj).getSiteName() ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int compareTo(@NotNull final MPSite<?> o) {
 | 
			
		||||
        return getName().compareTo( o.getName() );
 | 
			
		||||
    public int compareTo(@Nonnull final MPSite<?> o) {
 | 
			
		||||
        return getSiteName().compareTo( o.getSiteName() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return strf( "{%s: %s}", getClass().getSimpleName(), getName() );
 | 
			
		||||
        return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ import javax.annotation.Nullable;
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2014-06-08
 | 
			
		||||
 */
 | 
			
		||||
public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable implements MPUser<S> {
 | 
			
		||||
public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeable implements MPUser<S> {
 | 
			
		||||
 | 
			
		||||
    protected final Logger        logger    = Logger.get( getClass() );
 | 
			
		||||
    private final   Set<Listener> listeners = new CopyOnWriteArraySet<>();
 | 
			
		||||
@@ -152,10 +152,11 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addSite(final S site) {
 | 
			
		||||
        sites.put( site.getName(), site );
 | 
			
		||||
    public S addSite(final S site) {
 | 
			
		||||
        sites.put( site.getSiteName(), site );
 | 
			
		||||
 | 
			
		||||
        setChanged();
 | 
			
		||||
        return site;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -173,11 +174,12 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImmutableCollection<S> findSites(final String query) {
 | 
			
		||||
    public ImmutableCollection<S> findSites(@Nullable final String query) {
 | 
			
		||||
        ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
 | 
			
		||||
        for (final S site : getSites())
 | 
			
		||||
            if (site.getName().startsWith( query ))
 | 
			
		||||
                results.add( site );
 | 
			
		||||
        if (query != null)
 | 
			
		||||
            for (final S site : getSites())
 | 
			
		||||
                if (site.getSiteName().startsWith( query ))
 | 
			
		||||
                    results.add( site );
 | 
			
		||||
 | 
			
		||||
        return results.build();
 | 
			
		||||
    }
 | 
			
		||||
@@ -211,7 +213,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int compareTo(final MPUser<?> o) {
 | 
			
		||||
    public int compareTo(@Nonnull final MPUser<?> o) {
 | 
			
		||||
        return getFullName().compareTo( o.getFullName() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,7 @@ import org.joda.time.ReadableInstant;
 | 
			
		||||
 * @author lhunath, 14-12-05
 | 
			
		||||
 */
 | 
			
		||||
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
 | 
			
		||||
public class MPFileSite extends MPBasicSite<MPFileQuestion> {
 | 
			
		||||
 | 
			
		||||
    private final MPFileUser user;
 | 
			
		||||
public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private String          url;
 | 
			
		||||
@@ -61,10 +59,9 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
 | 
			
		||||
                         @Nullable final MPResultType resultType, @Nullable final String resultState,
 | 
			
		||||
                         @Nullable final MPResultType loginType, @Nullable final String loginState,
 | 
			
		||||
                         @Nullable final String url, final int uses, final ReadableInstant lastUsed) {
 | 
			
		||||
        super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter,
 | 
			
		||||
        super( user, name, (algorithm == null)? user.getAlgorithm(): algorithm, counter,
 | 
			
		||||
               (resultType == null)? user.getDefaultType(): resultType, loginType );
 | 
			
		||||
 | 
			
		||||
        this.user = user;
 | 
			
		||||
        this.resultState = resultState;
 | 
			
		||||
        this.loginState = loginState;
 | 
			
		||||
        this.url = url;
 | 
			
		||||
@@ -94,23 +91,21 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
 | 
			
		||||
    public void use() {
 | 
			
		||||
        uses++;
 | 
			
		||||
        lastUsed = new Instant();
 | 
			
		||||
        user.use();
 | 
			
		||||
        getUser().use();
 | 
			
		||||
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getResult()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getResult( MPKeyPurpose.Authentication, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return getResult( keyPurpose, keyContext, getResultState() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getLogin()
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
@@ -153,14 +148,8 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPFileUser getUser() {
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int compareTo(final MPSite<?> o) {
 | 
			
		||||
    public int compareTo(@Nonnull final MPSite<?> o) {
 | 
			
		||||
        int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;
 | 
			
		||||
        if (comparison != 0)
 | 
			
		||||
            return comparison;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPUser;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import org.joda.time.Instant;
 | 
			
		||||
import org.joda.time.ReadableInstant;
 | 
			
		||||
@@ -163,6 +164,11 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPFileSite addSite(final String siteName) {
 | 
			
		||||
        return addSite( new MPFileSite( this, siteName ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onChanged() {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -180,7 +186,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int compareTo(final MPUser<?> o) {
 | 
			
		||||
    public int compareTo(@Nonnull final MPUser<?> o) {
 | 
			
		||||
        int comparison = (o instanceof MPFileUser)? ((MPFileUser) o).getLastUsed().compareTo( getLastUsed() ): 0;
 | 
			
		||||
        if (comparison != 0)
 | 
			
		||||
            return comparison;
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ public class MPFlatMarshaller implements MPMarshaller {
 | 
			
		||||
                                        site.getAlgorithm().version().toInt(), // algorithm
 | 
			
		||||
                                        site.getCounter().intValue() ), // counter
 | 
			
		||||
                                  ifNotNullElse( loginName, "" ), // loginName
 | 
			
		||||
                                  site.getName(), // siteName
 | 
			
		||||
                                  site.getSiteName(), // siteName
 | 
			
		||||
                                  ifNotNullElse( password, "" ) // password
 | 
			
		||||
            ) );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
                // Clear Text
 | 
			
		||||
                content = modelSite.getResult();
 | 
			
		||||
                loginContent = modelUser.getMasterKey().siteResult(
 | 
			
		||||
                        modelSite.getName(), modelSite.getAlgorithm(), modelSite.getAlgorithm().mpw_default_counter(),
 | 
			
		||||
                        modelSite.getSiteName(), modelSite.getAlgorithm(), modelSite.getAlgorithm().mpw_default_counter(),
 | 
			
		||||
                        MPKeyPurpose.Identification, null, modelSite.getLoginType(), modelSite.getLoginState() );
 | 
			
		||||
            } else {
 | 
			
		||||
                // Redacted
 | 
			
		||||
@@ -100,9 +100,9 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
                    loginContent = modelSite.getLoginState();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Site site = sites.get( modelSite.getName() );
 | 
			
		||||
            Site site = sites.get( modelSite.getSiteName() );
 | 
			
		||||
            if (site == null)
 | 
			
		||||
                sites.put( modelSite.getName(), site = new Site() );
 | 
			
		||||
                sites.put( modelSite.getSiteName(), site = new Site() );
 | 
			
		||||
            site.type = modelSite.getResultType();
 | 
			
		||||
            site.counter = modelSite.getCounter().longValue();
 | 
			
		||||
            site.algorithm = modelSite.getAlgorithm().version();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user