diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index e5136125..1eb68e5a 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -83,6 +83,10 @@ public abstract class Components { return box; } + public static GradientPanel panel(@Nullable final LayoutManager layout) { + return panel( layout, null ); + } + public static GradientPanel panel(@Nullable final LayoutManager layout, @Nullable final Color color) { return new GradientPanel( layout, color ); } @@ -223,6 +227,11 @@ public abstract class Components { if (actionListener != null) actionListener.actionPerformed( e ); } + + @Override + public boolean isEnabled() { + return actionListener != null; + } } ); } @@ -233,6 +242,11 @@ public abstract class Components { if (actionListener != null) actionListener.actionPerformed( e ); } + + @Override + public boolean isEnabled() { + return actionListener != null; + } } ); iconButton.setFocusable( false ); @@ -441,6 +455,9 @@ public abstract class Components { public GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) { super( layout ); + if (getLayout() == null) + setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); + setGradientColor( gradientColor ); setBackground( null ); setAlignmentX( LEFT_ALIGNMENT ); @@ -459,7 +476,7 @@ public abstract class Components { @Override public void setBackground(@Nullable final Color bg) { super.setBackground( bg ); - updatePaint(); + setOpaque( bg != null ); } @Override @@ -469,8 +486,6 @@ public abstract class Components { } private void updatePaint() { - setOpaque( (getGradientColor() != null) || (getBackground() != null) ); - if (gradientColor == null) { paint = null; return; diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java index bd707a9c..cffcc92f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java @@ -128,6 +128,10 @@ public abstract class Res { return icon( "media/icon_user.png" ); } + public Icon lock() { + return icon( "media/icon_lock.png" ); + } + public Icon settings() { return icon( "media/icon_settings.png" ); } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java index eadbc75c..18937afc 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java @@ -2,10 +2,12 @@ package com.lyndir.masterpassword.gui.view; import static com.lyndir.masterpassword.util.Utilities.*; +import com.google.common.collect.ImmutableSortedSet; 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.model.MPUser; +import com.lyndir.masterpassword.model.impl.MPFileUser; import com.lyndir.masterpassword.model.impl.MPFileUserManager; import java.awt.*; import java.util.Collection; @@ -18,7 +20,7 @@ import javax.swing.*; * @author lhunath, 2018-07-14 */ @SuppressWarnings("serial") -public class FilesPanel extends JPanel { +public class FilesPanel extends JPanel implements MPFileUserManager.Listener { private final Collection listeners = new CopyOnWriteArraySet<>(); @@ -48,11 +50,8 @@ public class FilesPanel extends JPanel { // User Selection add( userField ); - } - public void reload() { - // TODO: Should we use a listener here instead? - usersModel.set( MPFileUserManager.get().reload() ); + MPFileUserManager.get().addListener( this ); } public boolean addListener(final Listener listener) { @@ -75,6 +74,11 @@ public class FilesPanel extends JPanel { listener.onUserSelected( user ); } + @Override + public void onFilesUpdated(final ImmutableSortedSet files) { + usersModel.set( files ); + } + public interface Listener { void onUserSelected(@Nullable MPUser user); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java index 50da1ea9..2ac3698f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java @@ -1,13 +1,12 @@ package com.lyndir.masterpassword.gui.view; import com.lyndir.lhunath.opal.system.logging.Logger; -import com.lyndir.masterpassword.gui.util.Res; import com.lyndir.masterpassword.gui.util.Components; -import com.lyndir.masterpassword.model.MPUser; +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.ComponentListener; -import javax.annotation.Nullable; import javax.swing.*; import javax.swing.border.BevelBorder; @@ -16,57 +15,48 @@ import javax.swing.border.BevelBorder; * @author lhunath, 2018-07-14 */ @SuppressWarnings("serial") -public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener, ComponentListener { +public class MasterPasswordFrame extends JFrame { private static final Logger logger = Logger.get( MasterPasswordFrame.class ); @SuppressWarnings("FieldCanBeLocal") - private final Components.GradientPanel root; - private final FilesPanel filesPanel = new FilesPanel(); - private final UserPanel userPanel = new UserPanel(); + private final Components.GradientPanel root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ); + private final FilesPanel filesPanel = new FilesPanel(); + private final JPanel userPanel = Components.panel( new BorderLayout( 0, 0 ) ); + private final UserContentPanel userContent = new UserContentPanel(); @SuppressWarnings("MagicNumber") public MasterPasswordFrame() { super( "Master Password" ); - setDefaultCloseOperation( DISPOSE_ON_CLOSE ); - setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) ); + setContentPane( root ); root.add( filesPanel ); root.add( Components.strut() ); - root.add( Components.borderPanel( + root.add( userPanel ); + + userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START ); + userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END ); + userPanel.add( Components.borderPanel( BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ), - Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userPanel ) ); + Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER ); - filesPanel.addListener( this ); - filesPanel.reload(); + filesPanel.addListener( userContent ); - addComponentListener( this ); - setPreferredSize( new Dimension( 640, 480 ) ); + addComponentListener( new ComponentHandler() ); + setPreferredSize( new Dimension( 800, 560 ) ); + setDefaultCloseOperation( DISPOSE_ON_CLOSE ); pack(); setLocationRelativeTo( null ); setLocationByPlatform( true ); } - @Override - public void onUserSelected(@Nullable final MPUser user) { - userPanel.setUser( user ); - } + private class ComponentHandler extends ComponentAdapter { - @Override - public void componentResized(final ComponentEvent e) { - } - - @Override - public void componentMoved(final ComponentEvent e) { - } - - @Override - public void componentShown(final ComponentEvent e) { - userPanel.transferFocus(); - } - - @Override - public void componentHidden(final ComponentEvent e) { + @Override + public void componentShown(final ComponentEvent e) { + MPFileUserManager.get().reload(); + userContent.transferFocus(); + } } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java similarity index 75% rename from platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java rename to platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java index 8b0e64b6..8734f5b6 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java @@ -11,8 +11,7 @@ import com.lyndir.masterpassword.*; 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; +import com.lyndir.masterpassword.model.impl.*; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; @@ -32,44 +31,38 @@ import javax.swing.event.DocumentListener; * @author lhunath, 2018-07-14 */ @SuppressWarnings("SerializableStoresNonSerializable") -public class UserPanel extends Components.GradientPanel implements MPUser.Listener { +public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPUser.Listener { - private static final Logger logger = Logger.get( UserPanel.class ); + private static final Random random = new Random(); + private static final Logger logger = Logger.get( UserContentPanel.class ); + private static final JButton iconButton = Components.button( Res.icons().user(), null ); + + private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS ); + private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS ); @Nullable - private MPUser user; + private MPUser listeningUser; - public UserPanel() { - super( new BorderLayout( Components.margin(), Components.margin() ), null ); + public UserContentPanel() { + userToolbar.setPreferredSize( iconButton.getPreferredSize() ); + siteToolbar.setPreferredSize( iconButton.getPreferredSize() ); + + setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); setBorder( Components.marginBorder() ); setUser( null ); } - public void setUser(@Nullable final MPUser user) { - if ((this.user != null) && !Objects.equals( this.user, user )) - this.user.removeListener( this ); + protected JComponent getUserToolbar() { + return userToolbar; + } - this.user = user; + protected JComponent getSiteToolbar() { + return siteToolbar; + } - if (this.user != null) - this.user.addListener( this ); - - Res.ui( () -> { - removeAll(); - if (this.user == null) - add( new NoUserPanel(), BorderLayout.CENTER ); - - else { - if (!this.user.isMasterKeyAvailable()) - add( new AuthenticateUserPanel( this.user ), BorderLayout.CENTER ); - - else - add( new AuthenticatedUserPanel( this.user ), BorderLayout.CENTER ); - } - - revalidate(); - transferFocus(); - } ); + @Override + public void onUserSelected(@Nullable final MPUser user) { + setUser( user ); } @Override @@ -82,7 +75,40 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen setUser( user ); } - private static final class NoUserPanel extends JPanel { + @Override + public void onUserInvalidated(final MPUser user) { + setUser( user ); + } + + private void setUser(@Nullable final MPUser user) { + Res.ui( () -> { + if (listeningUser != null) + listeningUser.removeListener( this ); + listeningUser = user; + + userToolbar.removeAll(); + siteToolbar.removeAll(); + removeAll(); + + if (user == null) + add( new NoUserPanel() ); + + else { + user.addListener( this ); + + if (!user.isMasterKeyAvailable()) + add( new AuthenticateUserPanel( user ) ); + + else + add( new AuthenticatedUserPanel( user ) ); + } + + revalidate(); + transferFocus(); + } ); + } + + private final class NoUserPanel extends JPanel { private NoUserPanel() { setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); @@ -94,13 +120,14 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen } - private static final class AuthenticateUserPanel extends JPanel implements ActionListener, DocumentListener { - - private static final Random random = new Random(); + private final class AuthenticateUserPanel extends JPanel implements ActionListener, DocumentListener { @Nonnull private final MPUser user; + private final JButton addButton = Components.button( Res.icons().add(), event -> addUser() ); + private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser() ); + private final JPasswordField masterPasswordField = Components.passwordField(); private final JLabel errorLabel = Components.label(); private final JLabel identiconLabel = Components.label( SwingConstants.CENTER ); @@ -112,7 +139,14 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen this.user = user; + userToolbar.add( addButton ); + userToolbar.add( deleteButton ); + add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); + add( Components.strut() ); + + add( identiconLabel ); + identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) ); add( Box.createGlue() ); add( Components.label( "Master Password:" ) ); @@ -122,14 +156,31 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen masterPasswordField.getDocument().addDocumentListener( this ); add( errorLabel ); errorLabel.setForeground( Res.colors().errorFg() ); - - add( Components.strut() ); - add( identiconLabel ); - identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) ); - add( Box.createGlue() ); } + private void addUser() { + Object fullName = JOptionPane.showInputDialog( + this, strf( "Enter your full legal name:" ), "Add User", + JOptionPane.QUESTION_MESSAGE, null, null, "Robert Lee Mitchell" ); + if (fullName == null) + return; + + setUser( MPFileUserManager.get().add( fullName.toString() ) ); + } + + private void deleteUser() { + MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null; + if (fileUser == null) + return; + + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( + this, strf( "Delete the user %s?

%s", + fileUser.getFullName(), fileUser.getFile().getName() ), + "Delete User", JOptionPane.YES_NO_OPTION )) + MPFileUserManager.get().delete( fileUser ); + } + @Override public void actionPerformed(final ActionEvent event) { updateIdenticon(); @@ -193,21 +244,25 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen } - private static final class AuthenticatedUserPanel extends JPanel implements KeyListener { + private final class AuthenticatedUserPanel extends JPanel implements KeyListener { public static final int SIZE_RESULT = 48; + private final JButton userButton = Components.button( Res.icons().user(), event -> showUserPreferences() ); + private final JButton logoutButton = Components.button( Res.icons().lock(), event -> logoutUser() ); + private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings() ); + private final JButton questionsButton = Components.button( Res.icons().question(), null ); + private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite() ); + @Nonnull private final MPUser user; - 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> sitesModel = + 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( null, this::updateSites ); + private final CollectionListModel> sitesModel = new CollectionListModel>().selection( this::showSiteResult ); - private final JList> sitesList = + private final JList> sitesList = Components.list( sitesModel, this::getSiteDescription ); private Future updateSitesJob; @@ -217,23 +272,20 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen this.user = user; - add( Components.panel( - Components.heading( user.getFullName(), SwingConstants.CENTER ), - Components.panel( - BoxLayout.LINE_AXIS, - Box.createGlue(), - Components.button( Res.icons().user(), event -> showUserPreferences() ) ) ) ); + userToolbar.add( userButton ); + userToolbar.add( logoutButton ); + + siteToolbar.add( settingsButton ); + siteToolbar.add( questionsButton ); + siteToolbar.add( deleteButton ); + settingsButton.setEnabled( false ); + + add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); add( passwordLabel ); - add( Components.panel( - passwordField, - Components.panel( - BoxLayout.LINE_AXIS, - Box.createGlue(), - passwordButton ) ) ); + add( passwordField ); passwordField.setForeground( Res.colors().highlightFg() ); passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) ); - passwordButton.setVisible( false ); add( Box.createGlue() ); add( Components.strut() ); @@ -269,13 +321,16 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); } - public void showSiteSettings() { - ImmutableList.Builder components = ImmutableList.builder(); + public void logoutUser() { + user.invalidate(); + } + public void showSiteSettings() { MPSite site = sitesModel.getSelectedItem(); if (site == null) return; + ImmutableList.Builder components = ImmutableList.builder(); components.add( Components.label( "Algorithm:" ), Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name, site.getAlgorithm().version(), @@ -306,6 +361,14 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); } + public void deleteSite() { + MPSite site = sitesModel.getSelectedItem(); + if (site == null) + return; + + user.deleteSite( site ); + } + private String getSiteDescription(@Nonnull final MPSite site) { if (site instanceof MPNewSite) return strf( "%s <Add new site>", queryField.getText() ); @@ -334,7 +397,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen MPSite site = sitesModel.getSelectedItem(); if (site instanceof MPNewSite) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( - this, strf( "Remember the site [%s]?", site.getSiteName() ), + this, strf( "Remember the site %s?", site.getSiteName() ), "New Site", JOptionPane.YES_NO_OPTION )) { sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) ); useSite(); @@ -370,7 +433,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen Res.ui( () -> { passwordLabel.setText( " " ); passwordField.setText( " " ); - passwordButton.setVisible( false ); + settingsButton.setEnabled( false ); } ); return; } @@ -384,7 +447,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen Res.ui( () -> { passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) ); passwordField.setText( result ); - passwordButton.setVisible( true ); + settingsButton.setEnabled( true ); } ); } catch (final MPKeyUnavailableException | MPAlgorithmException e) { diff --git a/platform-independent/java/gui/src/main/resources/media/icon_lock-open.png b/platform-independent/java/gui/src/main/resources/media/icon_lock-open.png new file mode 100644 index 00000000..1722d1e6 Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_lock-open.png differ diff --git a/platform-independent/java/gui/src/main/resources/media/icon_lock-open@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_lock-open@2x.png new file mode 100644 index 00000000..d7dad4cb Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_lock-open@2x.png differ diff --git a/platform-independent/java/gui/src/main/resources/media/icon_lock.png b/platform-independent/java/gui/src/main/resources/media/icon_lock.png new file mode 100644 index 00000000..2d15f826 Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_lock.png differ diff --git a/platform-independent/java/gui/src/main/resources/media/icon_lock@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_lock@2x.png new file mode 100644 index 00000000..d54aae42 Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_lock@2x.png differ diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java index 115ad20a..110adbac 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java @@ -94,9 +94,9 @@ public interface MPSite extends Comparable> { @Nonnull MPUser getUser(); - void addQuestion(Q question); + boolean addQuestion(Q question); - void deleteQuestion(Q question); + boolean deleteQuestion(Q question); @Nonnull Collection getQuestions(); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java index 5da8f400..7d352cca 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java @@ -77,6 +77,8 @@ public interface MPUser> extends Comparable> { void authenticate(MPMasterKey masterKey) throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException; + void invalidate(); + boolean isMasterKeyAvailable(); @Nonnull @@ -90,7 +92,7 @@ public interface MPUser> extends Comparable> { @Nonnull S addSite(S site); - void deleteSite(S site); + boolean deleteSite(MPSite site); @Nonnull Collection getSites(); @@ -107,5 +109,7 @@ public interface MPUser> extends Comparable> { void onUserUpdated(MPUser user); void onUserAuthenticated(MPUser user); + + void onUserInvalidated(MPUser user); } } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java index cba4538b..430f6ab7 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java @@ -55,8 +55,10 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion { @Override public void setType(final MPResultType type) { - this.type = type; + if (Objects.equals(this.type, type)) + return; + this.type = type; setChanged(); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java index a9aabfb5..f79d4c6d 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java @@ -73,8 +73,10 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext @Override public void setAlgorithm(final MPAlgorithm algorithm) { - this.algorithm = algorithm; + if (Objects.equals(this.algorithm, algorithm)) + return; + this.algorithm = algorithm; setChanged(); } @@ -86,8 +88,10 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext @Override public void setCounter(final UnsignedInteger counter) { - this.counter = counter; + if (Objects.equals(this.counter, counter)) + return; + this.counter = counter; setChanged(); } @@ -99,8 +103,10 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext @Override public void setResultType(final MPResultType resultType) { - this.resultType = resultType; + if (Objects.equals(this.resultType, resultType)) + return; + this.resultType = resultType; setChanged(); } @@ -112,8 +118,10 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext @Override public void setLoginType(@Nullable final MPResultType loginType) { - this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() ); + if (Objects.equals(this.loginType, loginType)) + return; + this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() ); setChanged(); } @@ -152,17 +160,21 @@ public abstract class MPBasicSite, Q extends MPQuestion> ext } @Override - public void addQuestion(final Q question) { - questions.add( question ); + public boolean addQuestion(final Q question) { + if (!questions.add( question )) + return false; setChanged(); + return true; } @Override - public void deleteQuestion(final Q question) { - questions.remove( question ); + public boolean deleteQuestion(final Q question) { + if (!questions.remove( question )) + return false; setChanged(); + return true; } @Nonnull diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java index 1e153bfa..3e7642a9 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java @@ -25,8 +25,7 @@ import com.google.common.collect.ImmutableSortedSet; import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.masterpassword.*; -import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException; -import com.lyndir.masterpassword.model.MPUser; +import com.lyndir.masterpassword.model.*; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.Nonnull; @@ -66,8 +65,10 @@ public abstract class MPBasicUser> extends Changeabl @Override public void setAvatar(final int avatar) { - this.avatar = avatar; + if (Objects.equals(this.avatar, avatar)) + return; + this.avatar = avatar; setChanged(); } @@ -85,8 +86,10 @@ public abstract class MPBasicUser> extends Changeabl @Override public void setAlgorithm(final MPAlgorithm algorithm) { - this.algorithm = algorithm; + if (Objects.equals(this.algorithm, algorithm)) + return; + this.algorithm = algorithm; setChanged(); } @@ -136,6 +139,17 @@ public abstract class MPBasicUser> extends Changeabl listener.onUserAuthenticated( this ); } + @Override + public void invalidate() { + if (masterKey == null) + return; + + this.masterKey = null; + + for (final Listener listener : listeners) + listener.onUserInvalidated( this ); + } + @Override public boolean isMasterKeyAvailable() { return masterKey != null; @@ -151,6 +165,7 @@ public abstract class MPBasicUser> extends Changeabl return masterKey; } + @Nonnull @Override public S addSite(final S site) { sites.put( site.getSiteName(), site ); @@ -160,10 +175,12 @@ public abstract class MPBasicUser> extends Changeabl } @Override - public void deleteSite(final S site) { - sites.values().remove( site ); + public boolean deleteSite(final MPSite site) { + if (!sites.values().remove( site )) + return false; setChanged(); + return true; } @Nonnull diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java index 74ed7bad..974f89fa 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java @@ -21,6 +21,7 @@ package com.lyndir.masterpassword.model.impl; import com.google.common.primitives.UnsignedInteger; import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.model.MPSite; +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joda.time.Instant; @@ -75,8 +76,10 @@ public class MPFileSite extends MPBasicSite { } public void setUrl(@Nullable final String url) { - this.url = url; + if (Objects.equals( this.url, url)) + return; + this.url = url; setChanged(); } @@ -92,7 +95,6 @@ public class MPFileSite extends MPBasicSite { uses++; lastUsed = new Instant(); getUser().use(); - setChanged(); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java index f3c8d7f9..a1c4b181 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java @@ -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 java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joda.time.Instant; @@ -98,8 +99,10 @@ public class MPFileUser extends MPBasicUser { } public void setFormat(final MPMarshalFormat format) { - this.format = format; + if (Objects.equals(this.format, format)) + return; + this.format = format; setChanged(); } @@ -108,8 +111,10 @@ public class MPFileUser extends MPBasicUser { } public void setContentMode(final MPMarshaller.ContentMode contentMode) { - this.contentMode = contentMode; + if (Objects.equals(this.contentMode, contentMode)) + return; + this.contentMode = contentMode; setChanged(); } @@ -118,8 +123,10 @@ public class MPFileUser extends MPBasicUser { } public void setDefaultType(final MPResultType defaultType) { - this.defaultType = defaultType; + if (Objects.equals(this.defaultType, defaultType)) + return; + this.defaultType = defaultType; setChanged(); } @@ -129,7 +136,6 @@ public class MPFileUser extends MPBasicUser { public void use() { lastUsed = new Instant(); - setChanged(); } @@ -159,7 +165,6 @@ public class MPFileUser extends MPBasicUser { if (keyID == null) { keyID = masterKey.getKeyID( getAlgorithm() ); - setChanged(); } } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUserManager.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUserManager.java index c84abba2..d90eff13 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUserManager.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUserManager.java @@ -25,8 +25,8 @@ import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.masterpassword.model.MPConstants; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; /** @@ -52,6 +52,7 @@ public class MPFileUserManager { } } + private final Collection listeners = new CopyOnWriteArraySet<>(); private final Map userByName = new HashMap<>(); private final File path; @@ -67,13 +68,13 @@ public class MPFileUserManager { this.path = path; } - public ImmutableSortedSet reload() { + public void reload() { userByName.clear(); File[] pathFiles; if ((!path.exists() && !path.mkdirs()) || ((pathFiles = path.listFiles()) == null)) { logger.err( "Couldn't create directory for user files: %s", path ); - return getFiles(); + return; } for (final File file : pathFiles) @@ -90,12 +91,14 @@ public class MPFileUserManager { logger.err( e, "Couldn't read user from: %s", file ); } - return getFiles(); + fireUpdated(); } public MPFileUser add(final String fullName) { MPFileUser user = new MPFileUser( fullName ); userByName.put( user.getFullName(), user ); + fireUpdated(); + return user; } @@ -104,8 +107,8 @@ public class MPFileUserManager { File userFile = user.getFile(); if (userFile.exists() && !userFile.delete()) logger.err( "Couldn't delete file: %s", userFile ); - else - userByName.values().remove( user ); + else if (userByName.values().remove( user )) + fireUpdated(); } public File getPath() { @@ -115,4 +118,27 @@ public class MPFileUserManager { public ImmutableSortedSet getFiles() { return ImmutableSortedSet.copyOf( userByName.values() ); } + + public boolean addListener(final Listener listener) { + return listeners.add( listener ); + } + + public boolean removeListener(final Listener listener) { + return listeners.remove( listener ); + } + + private void fireUpdated() { + if (listeners.isEmpty()) + return; + + ImmutableSortedSet files = getFiles(); + + for (final Listener listener : listeners) + listener.onFilesUpdated( files ); + } + + public interface Listener { + + void onFilesUpdated(ImmutableSortedSet files); + } }