diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
index 15c6d458..bd55e387 100644
--- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
+++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
@@ -44,7 +44,8 @@ public class MPMasterKey {
/**
* @param masterPassword The characters of the user's master password.
- * Note: this method destroys the contents of the array.
+ *
+ * @apiNote This method destroys the contents of the {@code masterPassword} array.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
public MPMasterKey(final String fullName, final char[] masterPassword) {
diff --git a/platform-independent/java/gui/build.gradle b/platform-independent/java/gui/build.gradle
index 8bfb918a..04c2134e 100644
--- a/platform-independent/java/gui/build.gradle
+++ b/platform-independent/java/gui/build.gradle
@@ -5,7 +5,7 @@ plugins {
}
description = 'Master Password GUI'
-mainClassName = 'com.lyndir.masterpassword.gui.Main'
+mainClassName = 'com.lyndir.masterpassword.gui.MasterPassword'
dependencies {
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java
new file mode 100644
index 00000000..3b5b3d3a
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java
@@ -0,0 +1,26 @@
+package com.lyndir.masterpassword.gui;
+
+import com.lyndir.lhunath.opal.system.logging.Logger;
+import com.lyndir.masterpassword.gui.util.Platform;
+import com.lyndir.masterpassword.gui.util.Res;
+import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
+
+
+/**
+ * @author lhunath, 2018-07-28
+ */
+public class GUI {
+
+ private static final Logger logger = Logger.get( GUI.class );
+
+ private final MasterPasswordFrame frame = new MasterPasswordFrame();
+
+ public GUI() {
+ Platform.get().installAppForegroundHandler( this::open );
+ Platform.get().installAppReopenHandler( this::open );
+ }
+
+ public void open() {
+ Res.ui( () -> frame.setVisible( true ) );
+ }
+}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Main.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java
similarity index 71%
rename from platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Main.java
rename to platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java
index 9fa1fbd9..aced49f7 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Main.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java
@@ -23,10 +23,14 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.google.common.io.ByteSource;
import com.lyndir.lhunath.opal.system.logging.Logger;
-import com.lyndir.masterpassword.gui.platform.BaseGUI;
+import com.lyndir.lhunath.opal.system.util.ObjectUtils;
+import com.lyndir.masterpassword.model.MPUser;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArraySet;
+import javax.annotation.Nullable;
import javax.swing.*;
@@ -35,33 +39,42 @@ import javax.swing.*;
*
* @author mbillemo
*/
-public final class Main {
+public final class MasterPassword {
@SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( Main.class );
+ private static final Logger logger = Logger.get( MasterPassword.class );
- public static void main(final String... args) {
-// Thread.setDefaultUncaughtExceptionHandler(
-// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
+ private static final MasterPassword instance = new MasterPassword();
- // Try and set the system look & feel, if available.
- try {
- UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
- }
- catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
- }
+ private final Collection listeners = new CopyOnWriteArraySet<>();
- // Check online to see if this version has been superseded.
- if (Config.get().checkForUpdates())
- checkUpdate();
+ @Nullable
+ private MPUser> activeUser;
- // Create a platform-specific GUI and open it.
- BaseGUI.createPlatformGUI().open();
+ public static MasterPassword get() {
+ return instance;
+ }
+
+ public boolean addListener(final Listener listener) {
+ return listeners.add( listener );
+ }
+
+ public boolean removeListener(final Listener listener) {
+ return listeners.remove( listener );
+ }
+
+ public void activateUser(final MPUser> user) {
+ if (ObjectUtils.equals( activeUser, user ))
+ return;
+
+ activeUser = user;
+ for (final Listener listener : listeners)
+ listener.onUserSelected( activeUser );
}
private static void checkUpdate() {
try {
- String implementationVersion = Main.class.getPackage().getImplementationVersion();
+ String implementationVersion = MasterPassword.class.getPackage().getImplementationVersion();
String latestVersion = new ByteSource() {
@Override
public InputStream openStream()
@@ -90,4 +103,27 @@ public final class Main {
logger.wrn( e, "Couldn't check for version update." );
}
}
+
+ public static void main(final String... args) {
+ // Thread.setDefaultUncaughtExceptionHandler(
+ // (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
+
+ // Try and set the system look & feel, if available.
+ try {
+ UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
+ }
+ catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
+ }
+
+ // Check online to see if this version has been superseded.
+ if (Config.get().checkForUpdates())
+ checkUpdate();
+
+ // Create a platform-specific GUI and open it.
+ new GUI().open();
+ }
+
+ public interface Listener {
+ void onUserSelected(@Nullable MPUser> user);
+ }
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/AppleGUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/AppleGUI.java
deleted file mode 100644
index 4a5731bf..00000000
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/AppleGUI.java
+++ /dev/null
@@ -1,62 +0,0 @@
-//==============================================================================
-// This file is part of Master Password.
-// Copyright (c) 2011-2017, Maarten Billemont.
-//
-// Master Password is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Master Password is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You can find a copy of the GNU General Public License in the
-// LICENSE file. Alternatively, see .
-//==============================================================================
-
-package com.lyndir.masterpassword.gui.platform;
-
-import com.apple.eawt.*;
-import com.google.common.base.Preconditions;
-import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
-import javax.swing.*;
-
-
-/**
- * @author lhunath, 2014-06-10
- */
-public class AppleGUI extends BaseGUI {
-
- static Application application = Preconditions.checkNotNull(
- Application.getApplication(), "Not an Apple Java application." );
-
- public AppleGUI() {
- application.addAppEventListener( new AppEventHandler() );
- }
-
- @Override
- protected MasterPasswordFrame createFrame() {
- MasterPasswordFrame frame = super.createFrame();
- frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
- return frame;
- }
-
- private class AppEventHandler implements AppForegroundListener, AppReOpenedListener {
-
- @Override
- public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
- }
-
- @Override
- public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
- open();
- }
-
- @Override
- public void appReOpened(final AppEvent.AppReOpenedEvent arg0) {
- open();
- }
- }
-}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/BaseGUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/BaseGUI.java
deleted file mode 100644
index f0750534..00000000
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/BaseGUI.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.lyndir.masterpassword.gui.platform;
-
-import com.lyndir.lhunath.opal.system.logging.Logger;
-import com.lyndir.lhunath.opal.system.util.TypeUtils;
-import com.lyndir.masterpassword.gui.util.Res;
-import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Optional;
-import javax.annotation.Nullable;
-
-
-/**
- * @author lhunath, 2018-07-28
- */
-public class BaseGUI {
-
- private static final Logger logger = Logger.get( BaseGUI.class );
-
- private final MasterPasswordFrame frame = createFrame();
-
- public static BaseGUI createPlatformGUI() {
- BaseGUI jdk9GUI = construct( "com.lyndir.masterpassword.gui.platform.JDK9GUI" );
- if (jdk9GUI != null)
- return jdk9GUI;
-
- BaseGUI appleGUI = construct( "com.lyndir.masterpassword.gui.platform.AppleGUI" );
- if (appleGUI != null)
- return appleGUI;
-
- // Use platform-independent GUI.
- return new BaseGUI();
- }
-
- @Nullable
- private static BaseGUI construct(final String typeName) {
- try {
- // AppleGUI adds support for macOS features.
- Optional> gui = TypeUtils.loadClass( typeName );
- if (gui.isPresent())
- return gui.get().getConstructor().newInstance();
- }
- catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
- }
- catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
- throw logger.bug( e );
- }
-
- return null;
- }
-
- protected MasterPasswordFrame createFrame() {
- return new MasterPasswordFrame();
- }
-
- public void open() {
- Res.ui( () -> frame.setVisible( true ) );
- }
-}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/JDK9GUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/JDK9GUI.java
deleted file mode 100644
index 7970f32b..00000000
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/JDK9GUI.java
+++ /dev/null
@@ -1,60 +0,0 @@
-//==============================================================================
-// This file is part of Master Password.
-// Copyright (c) 2011-2017, Maarten Billemont.
-//
-// Master Password is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Master Password is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You can find a copy of the GNU General Public License in the
-// LICENSE file. Alternatively, see .
-//==============================================================================
-
-package com.lyndir.masterpassword.gui.platform;
-
-import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
-import java.awt.*;
-import java.awt.desktop.*;
-import javax.swing.*;
-
-
-/**
- * @author lhunath, 2014-06-10
- */
-@SuppressWarnings("Since15")
-public class JDK9GUI extends BaseGUI {
-
- public JDK9GUI() {
- Desktop.getDesktop().addAppEventListener( new AppEventHandler() );
- }
-
- @Override
- protected MasterPasswordFrame createFrame() {
- MasterPasswordFrame frame = super.createFrame();
- frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
- return frame;
- }
-
- private class AppEventHandler implements AppForegroundListener, AppReopenedListener {
-
- @Override
- public void appRaisedToForeground(final AppForegroundEvent e) {
- open();
- }
-
- @Override
- public void appMovedToBackground(final AppForegroundEvent e) {
- }
-
- @Override
- public void appReopened(final AppReopenedEvent e) {
- open();
- }
- }
-}
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 229bd22b..99107ae9 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
@@ -21,6 +21,7 @@ package com.lyndir.masterpassword.gui.util;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
@@ -110,6 +111,29 @@ public abstract class Components {
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
}
+ @Nullable
+ public static File showLoadDialog(@Nullable final Component owner, final String title) {
+ return showFileDialog( owner, title, FileDialog.LOAD, null );
+ }
+
+ @Nullable
+ public static File showSaveDialog(@Nullable final Component owner, final String title, final String fileName) {
+ return showFileDialog( owner, title, FileDialog.SAVE, fileName );
+ }
+
+ @Nullable
+ private static File showFileDialog(@Nullable final Component owner, final String title,
+ final int mode, @Nullable final String fileName) {
+ FileDialog fileDialog = new FileDialog( JOptionPane.getFrameForComponent( owner ), title, mode );
+ fileDialog.setFile( fileName );
+ fileDialog.setLocationRelativeTo( owner );
+ fileDialog.setLocationByPlatform( true );
+ fileDialog.setVisible( true );
+
+ File[] selectedFiles = fileDialog.getFiles();
+ return ((selectedFiles != null) && (selectedFiles.length > 0))? selectedFiles[0]: null;
+ }
+
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null,
title, Dialog.ModalityType.DOCUMENT_MODAL );
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Platform.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Platform.java
new file mode 100644
index 00000000..5a7b8029
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Platform.java
@@ -0,0 +1,52 @@
+package com.lyndir.masterpassword.gui.util;
+
+import com.lyndir.lhunath.opal.system.logging.Logger;
+import com.lyndir.lhunath.opal.system.util.TypeUtils;
+import com.lyndir.masterpassword.gui.util.platform.BasePlatform;
+import com.lyndir.masterpassword.gui.util.platform.IPlatform;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+
+/**
+ * @author lhunath, 2018-07-29
+ */
+public final class Platform {
+
+ private static final Logger logger = Logger.get( Platform.class );
+ private static final IPlatform activePlatform;
+
+ static {
+ IPlatform tryPlatform;
+ if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.JDK9Platform" )))
+ activePlatform = tryPlatform;
+
+ else if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.ApplePlatform" )))
+ activePlatform = tryPlatform;
+
+ else
+ activePlatform = new BasePlatform();
+ }
+
+ @Nullable
+ private static T construct(final String typeName) {
+ try {
+ // AppleGUI adds support for macOS features.
+ Optional> gui = TypeUtils.loadClass( typeName );
+ if (gui.isPresent())
+ return gui.get().getConstructor().newInstance();
+ }
+ catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
+ }
+ catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
+ throw logger.bug( e );
+ }
+
+ return null;
+ }
+
+ public static IPlatform get() {
+ return activePlatform;
+ }
+}
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 e4bba494..c29896e1 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
@@ -136,6 +136,14 @@ public abstract class Res {
return icon( "media/icon_reset.png" );
}
+ public Icon import_() {
+ return icon( "media/icon_import.png" );
+ }
+
+ public Icon export() {
+ return icon( "media/icon_export.png" );
+ }
+
public Icon settings() {
return icon( "media/icon_settings.png" );
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/ApplePlatform.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/ApplePlatform.java
new file mode 100644
index 00000000..961bcc89
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/ApplePlatform.java
@@ -0,0 +1,49 @@
+package com.lyndir.masterpassword.gui.util.platform;
+
+import com.apple.eawt.*;
+import com.apple.eio.FileManager;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import java.io.File;
+import java.io.FileNotFoundException;
+
+
+/**
+ * @author lhunath, 2018-07-29
+ */
+public class ApplePlatform implements IPlatform {
+
+ static Application application = Preconditions.checkNotNull(
+ Application.getApplication(), "Not an Apple Java application." );
+
+ @Override
+ public boolean installAppForegroundHandler(final Runnable handler) {
+ application.addAppEventListener( new AppForegroundListener() {
+ @Override
+ public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
+ }
+
+ @Override
+ public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
+ handler.run();
+ }
+ } );
+ return true;
+ }
+
+ @Override
+ public boolean installAppReopenHandler(final Runnable handler) {
+ application.addAppEventListener( (AppReOpenedListener) e -> handler.run() );
+ return true;
+ }
+
+ @Override
+ public boolean show(final File file) {
+ try {
+ return FileManager.revealInFinder( file );
+ }
+ catch (final FileNotFoundException ignored) {
+ return false;
+ }
+ }
+}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/BasePlatform.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/BasePlatform.java
new file mode 100644
index 00000000..b6b41ea3
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/BasePlatform.java
@@ -0,0 +1,25 @@
+package com.lyndir.masterpassword.gui.util.platform;
+
+import java.io.File;
+
+
+/**
+ * @author lhunath, 2018-07-29
+ */
+public class BasePlatform implements IPlatform {
+
+ @Override
+ public boolean installAppForegroundHandler(final Runnable handler) {
+ return false;
+ }
+
+ @Override
+ public boolean installAppReopenHandler(final Runnable handler) {
+ return false;
+ }
+
+ @Override
+ public boolean show(final File file) {
+ return false;
+ }
+}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/IPlatform.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/IPlatform.java
new file mode 100644
index 00000000..a6f3d48e
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/IPlatform.java
@@ -0,0 +1,16 @@
+package com.lyndir.masterpassword.gui.util.platform;
+
+import java.io.File;
+
+
+/**
+ * @author lhunath, 2018-07-29
+ */
+public interface IPlatform {
+
+ boolean installAppForegroundHandler(Runnable handler);
+
+ boolean installAppReopenHandler(Runnable handler);
+
+ boolean show(File file);
+}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/JDK9Platform.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/JDK9Platform.java
new file mode 100644
index 00000000..1bc94782
--- /dev/null
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/JDK9Platform.java
@@ -0,0 +1,43 @@
+package com.lyndir.masterpassword.gui.util.platform;
+
+import java.awt.*;
+import java.awt.desktop.*;
+import java.io.File;
+
+
+/**
+ * @author lhunath, 2018-07-29
+ */
+@SuppressWarnings("Since15")
+public class JDK9Platform implements IPlatform {
+
+ @Override
+ public boolean installAppForegroundHandler(final Runnable handler) {
+ Desktop.getDesktop().addAppEventListener( new AppForegroundListener() {
+ @Override
+ public void appRaisedToForeground(final AppForegroundEvent e) {
+ handler.run();
+ }
+
+ @Override
+ public void appMovedToBackground(final AppForegroundEvent e) {
+ }
+ } );
+ return true;
+ }
+
+ @Override
+ public boolean installAppReopenHandler(final Runnable handler) {
+ Desktop.getDesktop().addAppEventListener( (AppReopenedListener) e -> handler.run() );
+ return true;
+ }
+
+ @Override
+ public boolean show(final File file) {
+ if (!file.exists())
+ return false;
+
+ Desktop.getDesktop().browseFileDirectory( file );
+ return true;
+ }
+}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/package-info.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/package-info.java
similarity index 95%
rename from platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/package-info.java
rename to platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/package-info.java
index 0168e33e..f4786cc2 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/platform/package-info.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/platform/package-info.java
@@ -20,6 +20,6 @@
* @author lhunath, 2018-04-26
*/
@ParametersAreNonnullByDefault
-package com.lyndir.masterpassword.gui.platform;
+package com.lyndir.masterpassword.gui.util.platform;
import javax.annotation.ParametersAreNonnullByDefault;
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 8aa0778c..c42a102c 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
@@ -3,6 +3,7 @@ package com.lyndir.masterpassword.gui.view;
import static com.lyndir.masterpassword.util.Utilities.*;
import com.google.common.collect.ImmutableSortedSet;
+import com.lyndir.masterpassword.gui.MasterPassword;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUser;
@@ -18,15 +19,13 @@ import javax.swing.*;
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("serial")
-public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
-
- private final Collection listeners = new CopyOnWriteArraySet<>();
+public class FilesPanel extends JPanel implements MPFileUserManager.Listener, MasterPassword.Listener {
private final JButton avatarButton = Components.button( Res.icons().avatar( 0 ), event -> setAvatar(),
"Click to change the user's avatar." );
private final CollectionListModel> usersModel =
- CollectionListModel.>copy( MPFileUserManager.get().getFiles() ).selection( this::setUser );
+ CollectionListModel.>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
private final JComboBox extends MPUser>> userField =
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
@@ -50,10 +49,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
add( userField );
MPFileUserManager.get().addListener( this );
- }
-
- public boolean addListener(final Listener listener) {
- return listeners.add( listener );
+ MasterPassword.get().addListener( this );
}
private void setAvatar() {
@@ -65,20 +61,14 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
avatarButton.setIcon( Res.icons().avatar( selectedUser.getAvatar() ) );
}
- public void setUser(@Nullable final MPUser> user) {
- avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
-
- for (final Listener listener : listeners)
- listener.onUserSelected( user );
- }
-
@Override
public void onFilesUpdated(final ImmutableSortedSet files) {
usersModel.set( files );
}
- public interface Listener {
-
- void onUserSelected(@Nullable MPUser> user);
+ @Override
+ public void onUserSelected(@Nullable final MPUser> user) {
+ usersModel.setSelectedItem( user );
+ avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
}
}
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 2ac3698f..baf3a66e 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
@@ -3,10 +3,12 @@ package com.lyndir.masterpassword.gui.view;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.util.Res;
+import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
+import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.border.BevelBorder;
@@ -40,8 +42,6 @@ public class MasterPasswordFrame extends JFrame {
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
- filesPanel.addListener( userContent );
-
addComponentListener( new ComponentHandler() );
setPreferredSize( new Dimension( 800, 560 ) );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
index bc7e1307..aa064d7e 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
@@ -8,15 +8,22 @@ import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.*;
+import com.lyndir.masterpassword.gui.MasterPassword;
import com.lyndir.masterpassword.gui.model.MPNewSite;
import com.lyndir.masterpassword.gui.util.*;
+import com.lyndir.masterpassword.gui.util.Platform;
import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.impl.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
import java.util.*;
+import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -31,20 +38,22 @@ import javax.swing.event.DocumentListener;
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("SerializableStoresNonSerializable")
-public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPUser.Listener {
+public class UserContentPanel extends JPanel implements MasterPassword.Listener, MPUser.Listener {
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, null );
- private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
- "Add a new user to Master Password." );
+ private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
+ "Add a new user to Master Password." );
+ private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(),
+ "Import a user from a backup file into Master Password." );
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
@Nullable
- private MPUser> activeUser;
+ private MPUser> showingUser;
private ContentMode contentMode;
public UserContentPanel() {
@@ -53,7 +62,9 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
setBorder( Components.marginBorder() );
- setUser( null );
+ showUser( null );
+
+ MasterPassword.get().addListener( this );
}
protected JComponent getUserToolbar() {
@@ -66,52 +77,52 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
@Override
public void onUserSelected(@Nullable final MPUser> user) {
- setUser( user );
+ showUser( user );
}
@Override
public void onUserUpdated(final MPUser> user) {
- setUser( user );
+ showUser( user );
}
@Override
public void onUserAuthenticated(final MPUser> user) {
- setUser( user );
+ showUser( user );
}
@Override
public void onUserInvalidated(final MPUser> user) {
- setUser( user );
+ showUser( user );
}
- private void setUser(@Nullable final MPUser> user) {
+ private void showUser(@Nullable final MPUser> user) {
Res.ui( () -> {
- if (activeUser != null)
- activeUser.removeListener( this );
+ if (showingUser != null)
+ showingUser.removeListener( this );
ContentMode newContentMode = ContentMode.getContentMode( user );
- if ((newContentMode != contentMode) || !ObjectUtils.equals( activeUser, user )) {
+ if ((newContentMode != contentMode) || !ObjectUtils.equals( showingUser, user )) {
userToolbar.removeAll();
siteToolbar.removeAll();
removeAll();
- activeUser = user;
+ showingUser = user;
switch (contentMode = newContentMode) {
case NO_USER:
add( new NoUserPanel() );
break;
case AUTHENTICATE:
- add( new AuthenticateUserPanel( Preconditions.checkNotNull( activeUser ) ) );
+ add( new AuthenticateUserPanel( Preconditions.checkNotNull( showingUser ) ) );
break;
case AUTHENTICATED:
- add( new AuthenticatedUserPanel( Preconditions.checkNotNull( activeUser ) ) );
+ add( new AuthenticatedUserPanel( Preconditions.checkNotNull( showingUser ) ) );
break;
}
revalidate();
transferFocus();
}
- if (activeUser != null)
- activeUser.addListener( this );
+ if (showingUser != null)
+ showingUser.addListener( this );
} );
}
@@ -122,7 +133,70 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
if (fullName == null)
return;
- setUser( MPFileUserManager.get().add( fullName.toString() ) );
+ MasterPassword.get().activateUser( MPFileUserManager.get().add( fullName.toString() ) );
+ }
+
+ private void importUser() {
+ File importFile = Components.showLoadDialog( this, "Import User File" );
+ if (importFile == null)
+ return;
+
+ try {
+ MPFileUser importUser = MPFileUser.load( importFile );
+ if (importUser == null) {
+ JOptionPane.showMessageDialog(
+ this, "Not a Master Password file.",
+ "Import Failed", JOptionPane.ERROR_MESSAGE );
+ return;
+ }
+
+ JPasswordField passwordField = Components.passwordField();
+ if (JOptionPane.OK_OPTION == Components.showDialog( this, "Import User", new JOptionPane( Components.panel(
+ BoxLayout.PAGE_AXIS,
+ Components.label( strf( "Enter the master password to import %s:",
+ importUser.getFullName() ) ),
+ Components.strut(),
+ passwordField ), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) {
+ @Override
+ public void selectInitialValue() {
+ passwordField.requestFocusInWindow();
+ }
+ } )) {
+ try {
+ importUser.authenticate( passwordField.getPassword() );
+ Optional existingUser = MPFileUserManager.get().getFiles().stream().filter(
+ user -> user.getFullName().equalsIgnoreCase( importUser.getFullName() ) ).findFirst();
+ if (existingUser.isPresent() && (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
+ this,
+ strf( "Importing user %s from this file will replace the existing user with the imported one.
"
+ + "Are you sure?
"
+ + "Existing user last modified: %s
Imported user last modified: %s",
+ importUser.getFullName(),
+ Res.format( existingUser.get().getLastUsed() ),
+ Res.format( importUser.getLastUsed() ) ) )))
+ return;
+
+ MasterPassword.get().activateUser( MPFileUserManager.get().add( importUser ) );
+ }
+ catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
+ JOptionPane.showMessageDialog(
+ this, e.getLocalizedMessage(),
+ "Import Failed", JOptionPane.ERROR_MESSAGE );
+ }
+ }
+ }
+ catch (final IOException e) {
+ logger.err( e, "While reading user import file." );
+ JOptionPane.showMessageDialog(
+ this, strf( "Couldn't read import file:
%s
.", e.getLocalizedMessage() ),
+ "Import Failed", JOptionPane.ERROR_MESSAGE );
+ }
+ catch (MPMarshalException e) {
+ logger.err( e, "While parsing user import file." );
+ JOptionPane.showMessageDialog(
+ this, strf( "Couldn't parse import file:
%s
.", e.getLocalizedMessage() ),
+ "Import Failed", JOptionPane.ERROR_MESSAGE );
+ }
}
private enum ContentMode {
@@ -147,6 +221,7 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
userToolbar.add( addButton );
+ userToolbar.add( importButton );
add( Box.createGlue() );
add( Components.heading( "Select a user to proceed." ) );
@@ -160,6 +235,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
@Nonnull
private final MPUser> user;
+ private final JButton exportButton = Components.button( Res.icons().export(), event -> exportUser(),
+ "Export this user to a backup file." );
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
"Delete this user from Master Password." );
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
@@ -177,6 +254,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
this.user = user;
userToolbar.add( addButton );
+ userToolbar.add( importButton );
+ userToolbar.add( exportButton );
userToolbar.add( deleteButton );
userToolbar.add( resetButton );
@@ -197,6 +276,27 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
add( Box.createGlue() );
}
+ private void exportUser() {
+ MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
+ if (fileUser == null)
+ return;
+
+ File exportFile = Components.showSaveDialog( this, "Export User File", fileUser.getFile().getName() );
+ if (exportFile == null)
+ return;
+
+ try {
+ Platform.get().show(
+ Files.copy( fileUser.getFile().toPath(), exportFile.toPath(),
+ StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES ).toFile() );
+ }
+ catch (final IOException e) {
+ JOptionPane.showMessageDialog(
+ this, e.getLocalizedMessage(),
+ "Export Failed", JOptionPane.ERROR_MESSAGE );
+ }
+ }
+
private void deleteUser() {
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
if (fileUser == null)
@@ -372,7 +472,17 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
sitesModel.registerList( sitesList );
add( Box.createGlue() );
- user.addListener( this );
+ addComponentListener( new ComponentAdapter() {
+ @Override
+ public void componentShown(final ComponentEvent e) {
+ user.addListener( AuthenticatedUserPanel.this );
+ }
+
+ @Override
+ public void componentHidden(final ComponentEvent e) {
+ user.removeListener( AuthenticatedUserPanel.this );
+ }
+ } );
}
public void showUserPreferences() {
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_export.png b/platform-independent/java/gui/src/main/resources/media/icon_export.png
new file mode 100644
index 00000000..d1d97fb2
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_export.png differ
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_export@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_export@2x.png
new file mode 100644
index 00000000..37c9e4f6
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_export@2x.png differ
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_import.png b/platform-independent/java/gui/src/main/resources/media/icon_import.png
new file mode 100644
index 00000000..5cdfd6e0
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_import.png differ
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_import@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_import@2x.png
new file mode 100644
index 00000000..e8a2650a
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_import@2x.png differ
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 e9be467c..f8242940 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
@@ -55,12 +55,11 @@ public interface MPUser> extends Comparable> {
/**
* Performs an authentication attempt against the keyID for this user.
*
- * Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
- *
* @param masterPassword The password to authenticate with.
- * You cannot re-use this array after passing it in, authentication will destroy its contents.
*
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
+ * @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
+ * This method destroys the contents of the {@code masterPassword} array.
*/
void authenticate(char[] masterPassword)
throws MPIncorrectMasterPasswordException, MPAlgorithmException;
@@ -68,11 +67,10 @@ public interface MPUser> extends Comparable> {
/**
* Performs an authentication attempt against the keyID for this user.
*
- * Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
- *
* @param masterKey The master key to authenticate with.
*
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
+ * @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
*/
void authenticate(MPMasterKey masterKey)
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
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 eef907c2..6821eeaf 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
@@ -37,8 +37,9 @@ import javax.annotation.Nullable;
*/
public abstract class MPBasicUser> extends Changeable implements MPUser {
- protected final Logger logger = Logger.get( getClass() );
- private final Set listeners = new CopyOnWriteArraySet<>();
+ private static final Logger logger = Logger.get( MPBasicUser.class );
+
+ private final Set listeners = new CopyOnWriteArraySet<>();
private int avatar;
private final String fullName;
@@ -65,7 +66,7 @@ public abstract class MPBasicUser> extends Changeabl
@Override
public void setAvatar(final int avatar) {
- if (Objects.equals(this.avatar, avatar))
+ if (Objects.equals( this.avatar, avatar ))
return;
this.avatar = avatar;
@@ -86,7 +87,7 @@ public abstract class MPBasicUser> extends Changeabl
@Override
public void setAlgorithm(final MPAlgorithm algorithm) {
- if (Objects.equals(this.algorithm, algorithm))
+ if (Objects.equals( this.algorithm, algorithm ))
return;
this.algorithm = algorithm;
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 acbdf2bc..f8fd88a2 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
@@ -18,6 +18,7 @@
package com.lyndir.masterpassword.model.impl;
+import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import com.lyndir.masterpassword.model.MPUser;
@@ -36,6 +37,8 @@ import org.joda.time.ReadableInstant;
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
public class MPFileUser extends MPBasicUser {
+ private static final Logger logger = Logger.get( MPFileUser.class );
+
@Nullable
private byte[] keyID;
private File path;
@@ -46,13 +49,23 @@ public class MPFileUser extends MPBasicUser {
private ReadableInstant lastUsed;
private boolean complete;
- public MPFileUser(final String fullName) {
- this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
+ @Nullable
+ public static MPFileUser load(final File file)
+ throws IOException, MPMarshalException {
+ for (final MPMarshalFormat format : MPMarshalFormat.values())
+ if (file.getName().endsWith( format.fileSuffix() ))
+ return format.unmarshaller().readUser( file );
+
+ return null;
}
- public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
+ public MPFileUser(final String fullName, final File path) {
+ this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), path );
+ }
+
+ public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File path) {
this( fullName, keyID, algorithm, 0, null, new Instant(),
- MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, MPFileUserManager.get().getPath() );
+ MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, path );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
@@ -74,6 +87,10 @@ public class MPFileUser extends MPBasicUser {
return (keyID == null)? null: keyID.clone();
}
+ public void setPath(final File path) {
+ this.path = path;
+ }
+
@Override
public void setAlgorithm(final MPAlgorithm algorithm) {
if (!algorithm.equals( getAlgorithm() ) && (keyID != null)) {
@@ -99,7 +116,7 @@ public class MPFileUser extends MPBasicUser {
}
public void setFormat(final MPMarshalFormat format) {
- if (Objects.equals(this.format, format))
+ if (Objects.equals( this.format, format ))
return;
this.format = format;
@@ -111,7 +128,7 @@ public class MPFileUser extends MPBasicUser {
}
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
- if (Objects.equals(this.contentMode, contentMode))
+ if (Objects.equals( this.contentMode, contentMode ))
return;
this.contentMode = contentMode;
@@ -123,7 +140,7 @@ public class MPFileUser extends MPBasicUser {
}
public void setDefaultType(final MPResultType defaultType) {
- if (Objects.equals(this.defaultType, defaultType))
+ if (Objects.equals( this.defaultType, defaultType ))
return;
this.defaultType = defaultType;
@@ -169,6 +186,19 @@ public class MPFileUser extends MPBasicUser {
}
}
+ public void save() {
+ try {
+ if (isComplete())
+ getFormat().marshaller().marshall( this );
+ }
+ catch (final MPKeyUnavailableException e) {
+ logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
+ }
+ catch (final IOException | MPMarshalException | MPAlgorithmException e) {
+ logger.err( e, "Unable to write out changes for user: %s", this );
+ }
+ }
+
@Override
public void reset() {
keyID = null;
@@ -183,16 +213,7 @@ public class MPFileUser extends MPBasicUser {
@Override
protected void onChanged() {
- try {
- if (isComplete())
- getFormat().marshaller().marshall( this );
- }
- catch (final MPKeyUnavailableException e) {
- logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
- }
- catch (final IOException | MPMarshalException | MPAlgorithmException e) {
- logger.err( e, "Unable to write out changes for user: %s", this );
- }
+ save();
super.onChanged();
}
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 d90eff13..b3d260dd 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
@@ -78,31 +78,40 @@ public class MPFileUserManager {
}
for (final File file : pathFiles)
- for (final MPMarshalFormat format : MPMarshalFormat.values())
- if (file.getName().endsWith( format.fileSuffix() ))
- try {
- MPFileUser user = format.unmarshaller().readUser( file );
- MPFileUser previousUser = userByName.put( user.getFullName(), user );
- if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
- userByName.put( previousUser.getFullName(), previousUser );
- break;
- }
- catch (final IOException | MPMarshalException e) {
- logger.err( e, "Couldn't read user from: %s", file );
- }
+ try {
+ MPFileUser user = MPFileUser.load( file );
+ if (user != null) {
+ MPFileUser previousUser = userByName.put( user.getFullName(), user );
+ if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
+ userByName.put( previousUser.getFullName(), previousUser );
+ }
+ }
+ catch (final IOException | MPMarshalException e) {
+ logger.err( e, "Couldn't read user from: %s", file );
+ }
fireUpdated();
}
public MPFileUser add(final String fullName) {
- MPFileUser user = new MPFileUser( fullName );
- userByName.put( user.getFullName(), user );
+ return add( new MPFileUser( fullName, getPath() ) );
+ }
+
+ public MPFileUser add(final MPFileUser user) {
+ user.setPath( getPath() );
+ user.save();
+
+ MPFileUser oldUser = userByName.put( user.getFullName(), user );
+ if (oldUser != null)
+ oldUser.invalidate();
fireUpdated();
return user;
}
public void delete(final MPFileUser user) {
+ user.invalidate();
+
// Remove deleted users.
File userFile = user.getFile();
if (userFile.exists() && !userFile.delete())