Compare commits
7 Commits
2.7-java-9
...
2.7-java-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
554c0129a2 | ||
|
|
78956beb08 | ||
|
|
39dacc8e5a | ||
|
|
34042e5462 | ||
|
|
0a386d6fad | ||
|
|
ff17a1d637 | ||
|
|
46fe919476 |
@@ -9,7 +9,7 @@ build_project:
|
||||
- "( ./lib/bin/build_libsodium-macos clean && ./lib/bin/build_libsodium-macos )"
|
||||
- "( ./lib/bin/build_libjson-c-macos clean && ./lib/bin/build_libjson-c-macos )"
|
||||
- "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )"
|
||||
- "( cd ./gradle && ./gradlew --stacktrace clean test )"
|
||||
- "( export JAVA_HOME=$(java_home -Fv 10 || java_home -Fv 9* ) && cd ./gradle && ./gradlew --stacktrace clean test )"
|
||||
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
|
||||
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' clean build )"
|
||||
tags:
|
||||
|
||||
@@ -2,7 +2,7 @@ allprojects {
|
||||
apply plugin: 'findbugs'
|
||||
|
||||
group = 'com.lyndir.masterpassword'
|
||||
version = '2.7.9'
|
||||
version = '2.7.10'
|
||||
|
||||
tasks.withType( JavaCompile ) {
|
||||
options.encoding = 'UTF-8'
|
||||
|
||||
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: bc737d41fa...3d04d775e0
@@ -3917,7 +3917,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/sh -e";
|
||||
shellScript = "exec Scripts/genassets";
|
||||
shellScript = "exec Scripts/genassets\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DAB7AE4C1F3D56AD00C856B1 /* ShellScript */ = {
|
||||
|
||||
@@ -120,21 +120,21 @@
|
||||
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
|
||||
if (indexPath.section == 0) {
|
||||
if (indexPath.item == 0) {
|
||||
MPGlobalAnswersCell *cell = [MPGlobalAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
MPGlobalAnswersCell *cell = [MPGlobalAnswersCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
[cell setSite:site];
|
||||
return cell;
|
||||
}
|
||||
if (indexPath.item == 1)
|
||||
return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
return [MPSendAnswersCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
if (indexPath.item == 2) {
|
||||
MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
cell.accessoryType = self.multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
|
||||
return cell;
|
||||
}
|
||||
Throw( @"Unsupported row index: %@", indexPath );
|
||||
}
|
||||
|
||||
MPAnswersQuestionCell *cell = [MPAnswersQuestionCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
MPAnswersQuestionCell *cell = [MPAnswersQuestionCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
MPSiteQuestionEntity *question = nil;
|
||||
if ([site.questions count] > indexPath.item)
|
||||
question = site.questions[indexPath.item];
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPGuideStepCell *cell = [MPGuideStepCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
||||
MPGuideStepCell *cell = [MPGuideStepCell dequeueFromCollectionView:collectionView indexPath:indexPath];
|
||||
cell.imageView.image = ((MPGuideStep *)self.steps[indexPath.item]).image;
|
||||
cell.contentView.frame = cell.bounds;
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
MPSiteCell *cell = [MPSiteCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
|
||||
MPSiteCell *cell = [MPSiteCell dequeueFromCollectionView:collectionView indexPath:indexPath];
|
||||
[cell setFuzzyGroups:self.fuzzyGroups];
|
||||
id item = self.dataSource[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
|
||||
if ([item isKindOfClass:[MPSiteEntity class]])
|
||||
|
||||
@@ -102,9 +102,9 @@ PearlEnum( MPDevelopmentFuelConsumption,
|
||||
SKProduct *product = content;
|
||||
MPStoreProductCell *cell;
|
||||
if ([product.productIdentifier isEqualToString:MPProductFuel])
|
||||
cell = [MPStoreFuelProductCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
cell = [MPStoreFuelProductCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
else
|
||||
cell = [MPStoreProductCell dequeueCellFromTableView:tableView indexPath:indexPath];
|
||||
cell = [MPStoreProductCell dequeueFromTableView:tableView indexPath:indexPath];
|
||||
[cell updateWithProduct:product transaction:self.transactions[product.productIdentifier]];
|
||||
|
||||
return cell;
|
||||
|
||||
@@ -147,7 +147,7 @@ public class MPMasterKey {
|
||||
* @return {@code null} if the result type is missing a required parameter.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
|
||||
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
|
||||
*/
|
||||
@Nullable
|
||||
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
@@ -185,7 +185,7 @@ public class MPMasterKey {
|
||||
* {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
|
||||
* @throws MPAlgorithmException An internal system or algorithm error has occurred.
|
||||
*/
|
||||
@Nonnull
|
||||
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.model.MPConfig;
|
||||
import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
|
||||
|
||||
@@ -26,15 +27,32 @@ import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
* @author lhunath, 2014-08-31
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class MPConfig {
|
||||
public class MPGuiConfig extends MPConfig {
|
||||
|
||||
private static final MPConfig instance = new MPConfig();
|
||||
|
||||
public static MPConfig get() {
|
||||
return instance;
|
||||
public static MPGuiConfig get() {
|
||||
return get( MPGuiConfig.class );
|
||||
}
|
||||
|
||||
Boolean checkForUpdates;
|
||||
Boolean stayResident;
|
||||
|
||||
public boolean checkForUpdates() {
|
||||
return ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
|
||||
return (checkForUpdates != null)? checkForUpdates:
|
||||
ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
|
||||
}
|
||||
|
||||
public void setCheckForUpdates(final boolean checkForUpdates) {
|
||||
this.checkForUpdates = checkForUpdates;
|
||||
MasterPassword.get().updateCheck();
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public boolean stayResident() {
|
||||
return (stayResident != null)? stayResident: false;
|
||||
}
|
||||
|
||||
public void setStayResident(final boolean stayResident) {
|
||||
this.stayResident = stayResident;
|
||||
setChanged();
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,10 @@ import javax.swing.*;
|
||||
public final class MasterPassword {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MasterPassword.class );
|
||||
|
||||
private static final Logger logger = Logger.get( MasterPassword.class );
|
||||
private static final MasterPassword instance = new MasterPassword();
|
||||
|
||||
private final Provider keyMaster = Provider.getCurrentProvider( true );
|
||||
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Nullable
|
||||
@@ -97,7 +97,29 @@ public final class MasterPassword {
|
||||
} );
|
||||
}
|
||||
|
||||
public void checkUpdate() {
|
||||
public static void main(final String... args) {
|
||||
//Thread.setDefaultUncaughtExceptionHandler(
|
||||
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
|
||||
|
||||
// Set the system look & feel, if available.
|
||||
try {
|
||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
||||
}
|
||||
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
// Create and open the UI.
|
||||
get().open();
|
||||
|
||||
// UI features.
|
||||
get().updateResidence();
|
||||
get().updateCheck();
|
||||
}
|
||||
|
||||
public void updateCheck() {
|
||||
if (!MPGuiConfig.get().checkForUpdates())
|
||||
return;
|
||||
|
||||
try {
|
||||
String implementationVersion = version();
|
||||
String latestVersion = new ByteSource() {
|
||||
@@ -127,26 +149,10 @@ public final class MasterPassword {
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
//Thread.setDefaultUncaughtExceptionHandler(
|
||||
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
|
||||
|
||||
// Try and set the system look & feel, if available.
|
||||
try {
|
||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
||||
Platform.get().installAppForegroundHandler( get()::open );
|
||||
Platform.get().installAppReopenHandler( get()::open );
|
||||
Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
|
||||
}
|
||||
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
// Create a platform-specific GUI and open it.
|
||||
get().open();
|
||||
|
||||
// Check online to see if this version has been superseded.
|
||||
if (MPConfig.get().checkForUpdates())
|
||||
get().checkUpdate();
|
||||
public void updateResidence() {
|
||||
Platform.get().installAppForegroundHandler( get()::open );
|
||||
Platform.get().installAppReopenHandler( get()::open );
|
||||
keyMaster.register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
|
||||
}
|
||||
|
||||
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
|
||||
|
||||
@@ -17,24 +17,47 @@ public class ApplePlatform implements IPlatform {
|
||||
private static final Application application = Preconditions.checkNotNull(
|
||||
Application.getApplication(), "Not an Apple Java application." );
|
||||
|
||||
private AppForegroundListener appForegroundHandler;
|
||||
private AppReOpenedListener appReopenHandler;
|
||||
|
||||
@Override
|
||||
public boolean installAppForegroundHandler(final Runnable handler) {
|
||||
application.addAppEventListener( new AppForegroundListener() {
|
||||
@Override
|
||||
public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
|
||||
}
|
||||
if (appForegroundHandler == null)
|
||||
application.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
|
||||
@Override
|
||||
public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
|
||||
handler.run();
|
||||
}
|
||||
} );
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
|
||||
handler.run();
|
||||
}
|
||||
} );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppForegroundHandler() {
|
||||
if (appForegroundHandler == null)
|
||||
return false;
|
||||
|
||||
application.removeAppEventListener( appForegroundHandler );
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean installAppReopenHandler(final Runnable handler) {
|
||||
application.addAppEventListener( (AppReOpenedListener) e -> handler.run() );
|
||||
application.addAppEventListener( appReopenHandler = e -> handler.run() );
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppReopenHandler() {
|
||||
if (appReopenHandler == null)
|
||||
return false;
|
||||
|
||||
application.removeAppEventListener( appReopenHandler );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,21 @@ public class BasePlatform implements IPlatform {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppForegroundHandler() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean installAppReopenHandler(final Runnable handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppReopenHandler() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestForeground() {
|
||||
return false;
|
||||
|
||||
@@ -12,8 +12,12 @@ public interface IPlatform {
|
||||
|
||||
boolean installAppForegroundHandler(Runnable handler);
|
||||
|
||||
boolean removeAppForegroundHandler();
|
||||
|
||||
boolean installAppReopenHandler(Runnable handler);
|
||||
|
||||
boolean removeAppReopenHandler();
|
||||
|
||||
boolean requestForeground();
|
||||
|
||||
boolean show(File file);
|
||||
|
||||
@@ -17,24 +17,49 @@ public class JDK9Platform implements IPlatform {
|
||||
private static final Logger logger = Logger.get( JDK9Platform.class );
|
||||
private static final Desktop desktop = Desktop.getDesktop();
|
||||
|
||||
private AppForegroundListener appForegroundHandler;
|
||||
private AppReopenedListener appReopenHandler;
|
||||
|
||||
@Override
|
||||
public boolean installAppForegroundHandler(final Runnable handler) {
|
||||
desktop.addAppEventListener( new AppForegroundListener() {
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppForegroundEvent e) {
|
||||
handler.run();
|
||||
}
|
||||
if (appForegroundHandler == null)
|
||||
desktop.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppForegroundEvent e) {
|
||||
handler.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appMovedToBackground(final AppForegroundEvent e) {
|
||||
}
|
||||
} );
|
||||
@Override
|
||||
public void appMovedToBackground(final AppForegroundEvent e) {
|
||||
}
|
||||
} );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppForegroundHandler() {
|
||||
if (appForegroundHandler == null)
|
||||
return false;
|
||||
|
||||
desktop.removeAppEventListener( appForegroundHandler );
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean installAppReopenHandler(final Runnable handler) {
|
||||
desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
|
||||
if (appReopenHandler == null)
|
||||
desktop.addAppEventListener( appReopenHandler = e -> handler.run() );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAppReopenHandler() {
|
||||
if (appReopenHandler == null)
|
||||
return false;
|
||||
|
||||
desktop.removeAppEventListener( appReopenHandler );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.gui.MPGuiConfig;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.Res;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
|
||||
@@ -19,7 +19,7 @@ public class MasterPasswordFrame extends JFrame {
|
||||
|
||||
private static final Logger logger = Logger.get( MasterPasswordFrame.class );
|
||||
|
||||
private final UserContentPanel userContent;
|
||||
private final UserContentPanel userContent;
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public MasterPasswordFrame() {
|
||||
@@ -39,6 +39,7 @@ public class MasterPasswordFrame extends JFrame {
|
||||
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
|
||||
|
||||
addComponentListener( new ComponentHandler() );
|
||||
addWindowListener( new WindowHandler() );
|
||||
setPreferredSize( new Dimension( 800, 560 ) );
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
pack();
|
||||
@@ -55,4 +56,14 @@ public class MasterPasswordFrame extends JFrame {
|
||||
userContent.transferFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class WindowHandler extends WindowAdapter {
|
||||
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
if (!MPGuiConfig.get().stayResident())
|
||||
System.exit( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.gui.MPGuiConstants;
|
||||
import com.lyndir.masterpassword.gui.MasterPassword;
|
||||
import com.lyndir.masterpassword.gui.*;
|
||||
import com.lyndir.masterpassword.gui.model.*;
|
||||
import com.lyndir.masterpassword.gui.util.*;
|
||||
import com.lyndir.masterpassword.gui.util.Platform;
|
||||
@@ -407,6 +406,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
Res.job( () -> {
|
||||
try {
|
||||
user.authenticate( masterPassword );
|
||||
|
||||
if (user instanceof MPFileUser)
|
||||
((MPFileUser) user).migrateTo( MPMarshalFormat.DEFAULT );
|
||||
}
|
||||
catch (final MPIncorrectMasterPasswordException e) {
|
||||
logger.wrn( e, "During user authentication for: %s", user );
|
||||
@@ -574,18 +576,25 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
|
||||
components.add( Components.label( "Default Algorithm:" ),
|
||||
Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
|
||||
user.getAlgorithm().version(),
|
||||
version -> user.setAlgorithm( version.getAlgorithm() ) ) );
|
||||
user.getAlgorithm().version(), version -> user.setAlgorithm( version.getAlgorithm() ) ) );
|
||||
|
||||
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
||||
if (fileUser != null) {
|
||||
components.add( Components.label( "Default Password Type:" ),
|
||||
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
|
||||
fileUser.getDefaultType(), fileUser::setDefaultType ),
|
||||
Components.strut() );
|
||||
components.add( Components.label( "Default Password Type:" ),
|
||||
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
|
||||
user.getPreferences().getDefaultType(), user.getPreferences()::setDefaultType ),
|
||||
Components.strut() );
|
||||
|
||||
components.add( Components.checkBox( "Hide Passwords", fileUser.isHidePasswords(), fileUser::setHidePasswords ) );
|
||||
}
|
||||
components.add( Components.checkBox( "Hide Passwords",
|
||||
user.getPreferences().isHidePasswords(), user.getPreferences()::setHidePasswords ) );
|
||||
|
||||
components.add( new JSeparator() );
|
||||
|
||||
components.add( Components.checkBox( "Check For Updates",
|
||||
MPGuiConfig.get().checkForUpdates(), MPGuiConfig.get()::setCheckForUpdates ) );
|
||||
|
||||
components.add( Components.checkBox( strf( "<html>Stay Resident (reactivate with <strong><code>%s+%s</code></strong>)",
|
||||
InputEvent.getModifiersExText( MPGuiConstants.ui_hotkey.getModifiers() ),
|
||||
KeyEvent.getKeyText( MPGuiConstants.ui_hotkey.getKeyCode() ) ),
|
||||
MPGuiConfig.get().stayResident(), MPGuiConfig.get()::setStayResident ) );
|
||||
|
||||
Components.showDialog( this, user.getFullName(), new JOptionPane( Components.panel(
|
||||
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
|
||||
@@ -612,14 +621,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
Components.strut() );
|
||||
|
||||
components.add( Components.label( "Password Type:" ),
|
||||
Components.comboBox( MPResultType.values(), type -> getTypeDescription(
|
||||
type, user.getDefaultType(), user.getAlgorithm().mpw_default_result_type() ),
|
||||
Components.comboBox( MPResultType.values(), type ->
|
||||
getTypeDescription( type, user.getPreferences().getDefaultType() ),
|
||||
site.getResultType(), site::setResultType ),
|
||||
Components.strut() );
|
||||
|
||||
components.add( Components.label( "Login Type:" ),
|
||||
Components.comboBox( MPResultType.values(), type -> getTypeDescription(
|
||||
type, user.getAlgorithm().mpw_default_login_type() ),
|
||||
Components.comboBox( MPResultType.values(), type ->
|
||||
getTypeDescription( type, user.getAlgorithm().mpw_default_login_type() ),
|
||||
site.getLoginType(), site::setLoginType ),
|
||||
Components.strut() );
|
||||
|
||||
@@ -650,8 +659,10 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
JList<MPQuery.Result<? extends MPQuestion>> questionsList =
|
||||
Components.list( questionsModel, this::getQuestionDescription );
|
||||
JTextField queryField = Components.textField( null, queryText -> Res.job( () -> {
|
||||
MPQuery query = new MPQuery( queryText );
|
||||
Collection<MPQuery.Result<? extends MPQuestion>> questionItems = new LinkedList<>( site.findQuestions( query ) );
|
||||
MPQuery query = new MPQuery( queryText );
|
||||
Collection<MPQuery.Result<? extends MPQuestion>> questionItems = new LinkedList<MPQuery.Result<? extends MPQuestion>>(
|
||||
query.find( site.getQuestions(), MPQuestion::getKeyword ) );
|
||||
|
||||
if (questionItems.stream().noneMatch( MPQuery.Result::isExact ))
|
||||
questionItems.add( MPQuery.Result.allOf( new MPNewQuestion( site, query.getQuery() ), query.getQuery() ) );
|
||||
|
||||
@@ -814,7 +825,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
private String getSiteDescription(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
|
||||
MPSite<?> site = (item != null)? item.getOption(): null;
|
||||
MPSite<?> site = (item != null)? item.getValue(): null;
|
||||
if (site == null)
|
||||
return " ";
|
||||
if (site instanceof MPNewSite)
|
||||
@@ -834,7 +845,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
private String getQuestionDescription(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
|
||||
MPQuestion question = (item != null)? item.getOption(): null;
|
||||
MPQuestion question = (item != null)? item.getValue(): null;
|
||||
if (question == null)
|
||||
return "<site>";
|
||||
if (question instanceof MPNewQuestion)
|
||||
@@ -876,7 +887,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
private void showSiteItem(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
|
||||
MPSite<?> site = (item != null)? item.getOption(): null;
|
||||
MPSite<?> site = (item != null)? item.getValue(): null;
|
||||
Res.ui( getSiteResult( site, showLogin ), result -> {
|
||||
if (!showLogin && (site != null))
|
||||
resultLabel.setText( (result != null)? strf( "Your password for %s:", site.getSiteName() ): " " );
|
||||
@@ -885,7 +896,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
|
||||
if ((result == null) || result.isEmpty())
|
||||
resultField.setText( " " );
|
||||
else if (!showLogin && (user instanceof MPFileUser) && ((MPFileUser) user).isHidePasswords())
|
||||
else if (!showLogin && user.getPreferences().isHidePasswords())
|
||||
resultField.setText( EACH_CHARACTER.matcher( result ).replaceAll( "•" ) );
|
||||
else
|
||||
resultField.setText( result );
|
||||
@@ -937,7 +948,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
private void showQuestionItem(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
|
||||
MPQuestion question = (item != null)? item.getOption(): null;
|
||||
MPQuestion question = (item != null)? item.getValue(): null;
|
||||
Res.ui( getQuestionResult( question ), answer -> {
|
||||
if ((answer == null) || (question == null))
|
||||
answerLabel.setText( " " );
|
||||
@@ -988,7 +999,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
if (selectedSite == null)
|
||||
return null;
|
||||
|
||||
return selectedSite.getOption();
|
||||
return selectedSite.getValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -997,7 +1008,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
if (selectedQuestion == null)
|
||||
return null;
|
||||
|
||||
return selectedQuestion.getOption();
|
||||
return selectedQuestion.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1022,14 +1033,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
|
||||
updateSitesJob = Res.job( () -> {
|
||||
MPQuery query = new MPQuery( queryText );
|
||||
Collection<MPQuery.Result<? extends MPSite<?>>> siteItems =
|
||||
new LinkedList<>( user.findSites( query ) );
|
||||
Collection<MPQuery.Result<? extends MPSite<?>>> siteItems = new LinkedList<MPQuery.Result<? extends MPSite<?>>>(
|
||||
query.find( user.getSites(), MPSite::getSiteName ) );
|
||||
|
||||
if (!Strings.isNullOrEmpty( queryText ))
|
||||
if (siteItems.stream().noneMatch( MPQuery.Result::isExact )) {
|
||||
MPQuery.Result<? extends MPSite<?>> selectedItem = sitesModel.getSelectedItem();
|
||||
if ((selectedItem != null) && user.equals( selectedItem.getOption().getUser() ) &&
|
||||
queryText.equals( selectedItem.getOption().getSiteName() ))
|
||||
if ((selectedItem != null) && user.equals( selectedItem.getValue().getUser() ) &&
|
||||
queryText.equals( selectedItem.getValue().getSiteName() ))
|
||||
siteItems.add( selectedItem );
|
||||
else
|
||||
siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) );
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.collect.ClassToInstanceMap;
|
||||
import com.google.common.collect.MutableClassToInstanceMap;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.model.impl.Changeable;
|
||||
import com.lyndir.masterpassword.model.impl.MPJSONAnyObject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-10-14
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class MPConfig extends MPJSONAnyObject {
|
||||
|
||||
private static final Logger logger = Logger.get( MPConfig.class );
|
||||
private static final ClassToInstanceMap<MPConfig> instances = MutableClassToInstanceMap.create();
|
||||
private static final File configFile = new File( rcDir(), "config.json" );
|
||||
|
||||
private final Changeable changeable = new Changeable() {
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
try {
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValue( configFile, MPConfig.this );
|
||||
instances.clear();
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.err( e, "While saving config to: %s", configFile );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected static synchronized <C extends MPConfig> C get(final Class<C> type) {
|
||||
C instance = instances.getInstance( type );
|
||||
|
||||
if (instance == null)
|
||||
if (configFile.exists())
|
||||
try {
|
||||
instances.putInstance( type, instance = objectMapper.readValue( configFile, type ) );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.wrn( e, "While reading config file: %s", configFile );
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
try {
|
||||
instance = type.getConstructor().newInstance();
|
||||
}
|
||||
catch (final ReflectiveOperationException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected void setChanged() {
|
||||
changeable.setChanged();
|
||||
}
|
||||
|
||||
public static MPConfig get() {
|
||||
return get( MPConfig.class );
|
||||
}
|
||||
|
||||
public static File rcDir() {
|
||||
String rcDir = System.getenv( MPModelConstants.env_rcDir );
|
||||
if (rcDir != null)
|
||||
return new File( rcDir );
|
||||
|
||||
String home = System.getProperty( "user.home" );
|
||||
if (home == null)
|
||||
home = System.getenv( "HOME" );
|
||||
|
||||
return new File( home, ".mpw.d" );
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,9 +31,8 @@ public class MPQuery {
|
||||
* @return {@code true} if this query is contained wholly inside the given {@code key}.
|
||||
*/
|
||||
@Nonnull
|
||||
public <T extends Comparable<? super T>> Optional<Result<T>> find(final T option, final Function<T, CharSequence> keyForOption) {
|
||||
CharSequence key = keyForOption.apply( option );
|
||||
Result<T> result = Result.noneOf( option, key );
|
||||
public <V> Optional<Result<V>> matches(final V value, final CharSequence key) {
|
||||
Result<V> result = Result.noneOf( value, key );
|
||||
if (query.isEmpty())
|
||||
return Optional.of( result );
|
||||
if (key.length() == 0)
|
||||
@@ -49,36 +49,49 @@ public class MPQuery {
|
||||
++k;
|
||||
}
|
||||
|
||||
// If query is consumed, the result is a hit.
|
||||
return (q >= query.length())? Optional.of( result ): Optional.empty();
|
||||
// If the match against the query broke before the end of the query, it failed.
|
||||
return (q < query.length())? Optional.empty(): Optional.of( result );
|
||||
}
|
||||
|
||||
public static class Result<T extends Comparable<? super T>> implements Comparable<Result<T>> {
|
||||
/**
|
||||
* @return Results for values that matched against the query, in the original values' order.
|
||||
*/
|
||||
@Nonnull
|
||||
public <V> ImmutableCollection<Result<? extends V>> find(final Iterable<? extends V> values,
|
||||
final Function<V, CharSequence> valueToKey) {
|
||||
ImmutableList.Builder<Result<? extends V>> results = ImmutableList.builder();
|
||||
for (final V value : values)
|
||||
matches( value, valueToKey.apply( value ) ).ifPresent( results::add );
|
||||
|
||||
private final T option;
|
||||
return results.build();
|
||||
}
|
||||
|
||||
public static class Result<V> {
|
||||
|
||||
private final V value;
|
||||
private final CharSequence key;
|
||||
private final boolean[] keyMatches;
|
||||
|
||||
Result(final T option, final CharSequence key) {
|
||||
this.option = option;
|
||||
Result(final V value, final CharSequence key) {
|
||||
this.value = value;
|
||||
this.key = key;
|
||||
|
||||
keyMatches = new boolean[key.length()];
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> Result<T> noneOf(final T option, final CharSequence key) {
|
||||
return new Result<>( option, key );
|
||||
public static <T> Result<T> noneOf(final T value, final CharSequence key) {
|
||||
return new Result<>( value, key );
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> Result<T> allOf(final T option, final CharSequence key) {
|
||||
Result<T> result = noneOf( option, key );
|
||||
public static <T> Result<T> allOf(final T value, final CharSequence key) {
|
||||
Result<T> result = noneOf( value, key );
|
||||
Arrays.fill( result.keyMatches, true );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public T getOption() {
|
||||
return option;
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -130,23 +143,18 @@ public class MPQuery {
|
||||
keyMatches[k] = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final Result<T> o) {
|
||||
return getOption().compareTo( o.getOption() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof Result))
|
||||
return false;
|
||||
|
||||
Result<?> r = (Result<?>) o;
|
||||
return Objects.equals( option, r.option ) && Objects.equals( key, r.key ) && Arrays.equals( keyMatches, r.keyMatches );
|
||||
return Objects.equals( value, r.value ) && Objects.equals( key, r.key ) && Arrays.equals( keyMatches, r.keyMatches );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getOption().hashCode();
|
||||
return getValue().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,14 +40,14 @@ public interface MPQuestion extends Comparable<MPQuestion> {
|
||||
|
||||
void setType(MPResultType type);
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
default String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getAnswer( null );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
String getAnswer(@Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Collection;
|
||||
@@ -118,7 +117,4 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
|
||||
@Nonnull
|
||||
Collection<Q> getQuestions();
|
||||
|
||||
@Nonnull
|
||||
ImmutableCollection<MPQuery.Result<Q>> findQuestions(MPQuery query);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Collection;
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -39,6 +38,9 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
@Nonnull
|
||||
String getFullName();
|
||||
|
||||
@Nonnull
|
||||
MPUserPreferences getPreferences();
|
||||
|
||||
// - Algorithm
|
||||
|
||||
@Nonnull
|
||||
@@ -46,11 +48,6 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
|
||||
void setAlgorithm(MPAlgorithm algorithm);
|
||||
|
||||
@Nullable
|
||||
default MPResultType getDefaultType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
byte[] getKeyID();
|
||||
|
||||
@@ -111,9 +108,6 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
@Nonnull
|
||||
Collection<S> getSites();
|
||||
|
||||
@Nonnull
|
||||
ImmutableCollection<MPQuery.Result<S>> findSites(MPQuery query);
|
||||
|
||||
void addListener(Listener listener);
|
||||
|
||||
void removeListener(Listener listener);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-10-13
|
||||
*/
|
||||
public interface MPUserPreferences {
|
||||
|
||||
MPResultType getDefaultType();
|
||||
|
||||
void setDefaultType(@Nullable MPResultType defaultType);
|
||||
|
||||
boolean isHidePasswords();
|
||||
|
||||
void setHidePasswords(boolean hidePasswords);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import java.util.concurrent.Executors;
|
||||
/**
|
||||
* @author lhunath, 2018-07-08
|
||||
*/
|
||||
public class Changeable {
|
||||
public abstract class Changeable {
|
||||
|
||||
private static final ExecutorService changeExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@@ -15,7 +15,9 @@ public class Changeable {
|
||||
private Grouping grouping = Grouping.APPLY;
|
||||
private boolean changed;
|
||||
|
||||
void setChanged() {
|
||||
protected abstract void onChanged();
|
||||
|
||||
public void setChanged() {
|
||||
synchronized (mutex) {
|
||||
if (changed)
|
||||
return;
|
||||
@@ -37,9 +39,6 @@ public class Changeable {
|
||||
} );
|
||||
}
|
||||
|
||||
protected void onChanged() {
|
||||
}
|
||||
|
||||
public void beginChanges() {
|
||||
synchronized (mutex) {
|
||||
grouping = Grouping.BATCH;
|
||||
|
||||
@@ -72,7 +72,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAnswer(@Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@@ -82,8 +82,6 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
if (site instanceof Changeable)
|
||||
((Changeable) site).setChanged();
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ package com.lyndir.masterpassword.model.impl;
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
@@ -39,7 +37,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
|
||||
private final U user;
|
||||
private final String siteName;
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
private final Collection<Q> questions = new TreeSet<>();
|
||||
|
||||
private MPAlgorithm algorithm;
|
||||
private UnsignedInteger counter;
|
||||
@@ -57,8 +55,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
this.siteName = siteName;
|
||||
this.algorithm = (algorithm != null)? algorithm: this.user.getAlgorithm();
|
||||
this.counter = (counter != null)? counter: this.algorithm.mpw_default_counter();
|
||||
this.resultType = (resultType != null)? resultType:
|
||||
ifNotNullElse( this.user.getDefaultType(), this.algorithm.mpw_default_result_type() );
|
||||
this.resultType = (resultType != null)? resultType: this.user.getPreferences().getDefaultType();
|
||||
this.loginType = (loginType != null)? loginType: this.algorithm.mpw_default_login_type();
|
||||
}
|
||||
|
||||
@@ -202,20 +199,8 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
return Collections.unmodifiableCollection( questions );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ImmutableCollection<MPQuery.Result<Q>> findQuestions(final MPQuery query) {
|
||||
ImmutableSortedSet.Builder<MPQuery.Result<Q>> results = ImmutableSortedSet.naturalOrder();
|
||||
for (final Q question : questions)
|
||||
query.find( question, MPQuestion::getKeyword ).ifPresent( results::add );
|
||||
|
||||
return results.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
if (user instanceof Changeable)
|
||||
((Changeable) user).setChanged();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
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.*;
|
||||
@@ -47,7 +45,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
@Nullable
|
||||
protected MPMasterKey masterKey;
|
||||
|
||||
private final Map<String, S> sites = new LinkedHashMap<>();
|
||||
private final Set<S> sites = new TreeSet<>();
|
||||
|
||||
protected MPBasicUser(final String fullName, final MPAlgorithm algorithm) {
|
||||
this( 0, fullName, algorithm );
|
||||
@@ -79,6 +77,12 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPUserPreferences getPreferences() {
|
||||
return new MPBasicUserPreferences<MPBasicUser<?>>( this );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
@@ -177,7 +181,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
@Nonnull
|
||||
@Override
|
||||
public S addSite(final S site) {
|
||||
sites.put( site.getSiteName(), site );
|
||||
sites.add( site );
|
||||
|
||||
setChanged();
|
||||
return site;
|
||||
@@ -185,7 +189,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
|
||||
@Override
|
||||
public boolean deleteSite(final MPSite<?> site) {
|
||||
if (!sites.values().remove( site ))
|
||||
if (!sites.remove( site ))
|
||||
return false;
|
||||
|
||||
setChanged();
|
||||
@@ -195,17 +199,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
@Nonnull
|
||||
@Override
|
||||
public Collection<S> getSites() {
|
||||
return Collections.unmodifiableCollection( sites.values() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ImmutableCollection<MPQuery.Result<S>> findSites(final MPQuery query) {
|
||||
ImmutableSortedSet.Builder<MPQuery.Result<S>> results = ImmutableSortedSet.naturalOrder();
|
||||
for (final S site : sites.values())
|
||||
query.find( site, MPSite::getSiteName ).ifPresent( results::add );
|
||||
|
||||
return results.build();
|
||||
return Collections.unmodifiableCollection( sites );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,8 +214,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
for (final Listener listener : listeners)
|
||||
listener.onUserUpdated( this );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.model.MPUserPreferences;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-10-13
|
||||
*/
|
||||
public class MPBasicUserPreferences<U extends MPBasicUser<?>> implements MPUserPreferences {
|
||||
|
||||
private final U user;
|
||||
|
||||
@Nullable
|
||||
private MPResultType defaultType;
|
||||
private boolean hidePasswords;
|
||||
|
||||
public MPBasicUserPreferences(final U user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
protected U getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType getDefaultType() {
|
||||
return (defaultType != null)? defaultType: user.getAlgorithm().mpw_default_result_type();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultType(@Nullable final MPResultType defaultType) {
|
||||
this.defaultType = defaultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidePasswords() {
|
||||
return hidePasswords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHidePasswords(final boolean hidePasswords) {
|
||||
this.hidePasswords = hidePasswords;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package com.lyndir.masterpassword.model.impl;
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
@@ -33,6 +32,7 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
@Nullable
|
||||
private String answerState;
|
||||
|
||||
@SuppressWarnings("TypeMayBeWeakened")
|
||||
public MPFileQuestion(final MPFileSite site, final String keyword,
|
||||
@Nullable final MPResultType type, @Nullable final String answerState) {
|
||||
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
@@ -45,7 +45,7 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
return answerState;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
@@ -20,11 +20,9 @@ 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;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
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;
|
||||
@@ -41,46 +39,48 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
|
||||
@Nullable
|
||||
private byte[] keyID;
|
||||
private File path;
|
||||
private File file;
|
||||
private MPMarshalFormat format;
|
||||
private MPMarshaller.ContentMode contentMode;
|
||||
private ReadableInstant lastUsed;
|
||||
private boolean complete;
|
||||
|
||||
private MPResultType defaultType;
|
||||
private ReadableInstant lastUsed;
|
||||
private boolean hidePasswords;
|
||||
private boolean complete;
|
||||
private final MPFileUserPreferences preferences;
|
||||
|
||||
@Nullable
|
||||
public static MPFileUser load(final File file)
|
||||
throws IOException, MPMarshalException {
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
if (file.getName().endsWith( format.fileSuffix() ))
|
||||
if (format.matches( file ))
|
||||
return format.unmarshaller().readUser( file );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, final File path) {
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), path );
|
||||
public MPFileUser(final String fullName, final File location) {
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), location );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File path) {
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File location) {
|
||||
this( fullName, keyID, algorithm, 0, null, new Instant(), false,
|
||||
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, path );
|
||||
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final int avatar,
|
||||
@Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords,
|
||||
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File path) {
|
||||
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
|
||||
super( avatar, fullName, algorithm );
|
||||
|
||||
this.keyID = (keyID != null)? keyID.clone(): null;
|
||||
this.defaultType = (defaultType != null)? defaultType: algorithm.mpw_default_result_type();
|
||||
this.lastUsed = lastUsed;
|
||||
this.hidePasswords = hidePasswords;
|
||||
this.path = path;
|
||||
this.preferences = new MPFileUserPreferences( this, defaultType, hidePasswords );
|
||||
this.format = format;
|
||||
this.contentMode = contentMode;
|
||||
|
||||
if (location.isDirectory())
|
||||
this.file = new File( location, getFullName() + getFormat().fileSuffix() );
|
||||
else
|
||||
this.file = location;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -89,8 +89,10 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
return (keyID == null)? null: keyID.clone();
|
||||
}
|
||||
|
||||
public void setPath(final File path) {
|
||||
this.path = path;
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPUserPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,39 +119,18 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(final MPMarshalFormat format) {
|
||||
if (Objects.equals( this.format, format ))
|
||||
return;
|
||||
|
||||
this.format = format;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public MPMarshaller.ContentMode getContentMode() {
|
||||
return contentMode;
|
||||
}
|
||||
|
||||
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
||||
if (Objects.equals( this.contentMode, contentMode ))
|
||||
if (this.contentMode == contentMode)
|
||||
return;
|
||||
|
||||
this.contentMode = contentMode;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType getDefaultType() {
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
public void setDefaultType(final MPResultType defaultType) {
|
||||
if (Objects.equals( this.defaultType, defaultType ))
|
||||
return;
|
||||
|
||||
this.defaultType = defaultType;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public ReadableInstant getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
@@ -159,18 +140,6 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public boolean isHidePasswords() {
|
||||
return hidePasswords;
|
||||
}
|
||||
|
||||
public void setHidePasswords(final boolean hidePasswords) {
|
||||
if (Objects.equals( this.hidePasswords, hidePasswords ))
|
||||
return;
|
||||
|
||||
this.hidePasswords = hidePasswords;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
protected boolean isComplete() {
|
||||
return complete;
|
||||
}
|
||||
@@ -180,7 +149,47 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return new File( path, getFullName() + getFormat().fileSuffix() );
|
||||
return file;
|
||||
}
|
||||
|
||||
public void migrateTo(final MPMarshalFormat format) {
|
||||
if (this.format == format)
|
||||
return;
|
||||
|
||||
migrateTo( file.getParentFile(), format );
|
||||
}
|
||||
|
||||
public void migrateTo(final File path) {
|
||||
migrateTo( path, format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file for this user to the given path using a standard user-derived filename (ie. {@code [full name].[format suffix]})
|
||||
*
|
||||
* The user's old file is either moved to the new or deleted. If the user's file was already at the destination, it doesn't change.
|
||||
* If a file already exists at the destination, it is overwritten.
|
||||
*/
|
||||
public void migrateTo(final File path, final MPMarshalFormat newFormat) {
|
||||
MPMarshalFormat oldFormat = format;
|
||||
File oldFile = file, newFile = new File( path, getFullName() + newFormat.fileSuffix() );
|
||||
|
||||
// If the format hasn't changed, migrate by moving the file: the contents doesn't need to change.
|
||||
if ((oldFormat == newFormat) && !oldFile.equals( newFile ) && oldFile.exists())
|
||||
if (!oldFile.renameTo( newFile ))
|
||||
logger.wrn( "Couldn't move %s to %s for migration.", oldFile, newFile );
|
||||
|
||||
this.format = newFormat;
|
||||
this.file = newFile;
|
||||
|
||||
// If the format has changed, save the new format into the new file and delete the old file. Revert if the user cannot be saved.
|
||||
if ((oldFormat != newFormat) && !oldFile.equals( newFile ))
|
||||
if (save()) {
|
||||
if (oldFile.exists() && !oldFile.delete())
|
||||
logger.wrn( "Couldn't delete %s after migration.", oldFile );
|
||||
} else {
|
||||
this.format = oldFormat;
|
||||
this.file = oldFile;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,10 +210,16 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
/**
|
||||
* @return {@code false} if the user is not fully loaded (complete), authenticated, or an issue prevented the marshalling.
|
||||
*/
|
||||
public boolean save() {
|
||||
if (!isComplete())
|
||||
return false;
|
||||
|
||||
try {
|
||||
if (isComplete())
|
||||
getFormat().marshaller().marshall( this );
|
||||
getFormat().marshaller().marshall( this );
|
||||
return true;
|
||||
}
|
||||
catch (final MPKeyUnavailableException e) {
|
||||
logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
|
||||
@@ -212,6 +227,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
catch (final IOException | MPMarshalException | MPAlgorithmException e) {
|
||||
logger.err( e, "Unable to write out changes for user: %s", this );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
import com.lyndir.masterpassword.model.MPConfig;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
@@ -38,19 +36,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
public class MPFileUserManager {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||
private static final MPFileUserManager instance;
|
||||
|
||||
static {
|
||||
String rcDir = System.getenv( MPModelConstants.env_rcDir );
|
||||
|
||||
if (rcDir != null)
|
||||
instance = create( new File( rcDir ) );
|
||||
else {
|
||||
String home = ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) );
|
||||
instance = create( new File( home, ".mpw.d" ) );
|
||||
}
|
||||
}
|
||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||
private static final MPFileUserManager instance = create( MPConfig.get().rcDir() );
|
||||
|
||||
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||
private final Map<String, MPFileUser> userByName = new HashMap<>();
|
||||
@@ -78,18 +65,17 @@ public class MPFileUserManager {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final File file : pathFiles)
|
||||
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 );
|
||||
}
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
for (final File file : pathFiles)
|
||||
if (format.matches( file ))
|
||||
try {
|
||||
MPFileUser user = MPFileUser.load( file );
|
||||
if (user != null)
|
||||
add( user );
|
||||
}
|
||||
catch (final IOException | MPMarshalException e) {
|
||||
logger.err( e, "Couldn't read user from: %s", file );
|
||||
}
|
||||
|
||||
fireUpdated();
|
||||
}
|
||||
@@ -99,12 +85,19 @@ public class MPFileUserManager {
|
||||
}
|
||||
|
||||
public MPFileUser add(final MPFileUser user) {
|
||||
user.setPath( getPath() );
|
||||
user.save();
|
||||
// We migrate in two steps to allow the first to complete even if the user is not in the right state to complete the latter.
|
||||
user.migrateTo( getPath() );
|
||||
user.migrateTo( MPMarshalFormat.DEFAULT );
|
||||
|
||||
MPFileUser oldUser = userByName.put( user.getFullName(), user );
|
||||
if (oldUser != null)
|
||||
if (oldUser != null) {
|
||||
oldUser.invalidate();
|
||||
|
||||
// Delete old user, it is replaced by the new one.
|
||||
if (!oldUser.getFile().equals( user.getFile() ) && oldUser.getFile().exists())
|
||||
if (!oldUser.getFile().delete())
|
||||
logger.err( "Couldn't delete file: %s, after replacing with: %s", oldUser.getFile(), user.getFile() );
|
||||
}
|
||||
fireUpdated();
|
||||
|
||||
return user;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-10-13
|
||||
*/
|
||||
public class MPFileUserPreferences extends MPBasicUserPreferences<MPFileUser> {
|
||||
|
||||
public MPFileUserPreferences(final MPFileUser user, @Nullable final MPResultType defaultType, final boolean hidePasswords) {
|
||||
super( user );
|
||||
|
||||
setDefaultType( defaultType );
|
||||
setHidePasswords( hidePasswords );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultType(@Nullable final MPResultType defaultType) {
|
||||
if (getDefaultType() == defaultType)
|
||||
return;
|
||||
|
||||
super.setDefaultType( defaultType );
|
||||
getUser().setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHidePasswords(final boolean hidePasswords) {
|
||||
if (Objects.equals( isHidePasswords(), hidePasswords ))
|
||||
return;
|
||||
|
||||
super.setHidePasswords( hidePasswords );
|
||||
getUser().setChanged();
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public class MPFlatMarshaller implements MPMarshaller {
|
||||
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
|
||||
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
|
||||
content.append( "# Algorithm: " ).append( user.getAlgorithm().version().toInt() ).append( '\n' );
|
||||
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
|
||||
content.append( "# Default Type: " ).append( user.getPreferences().getDefaultType().getType() ).append( '\n' );
|
||||
content.append( "# Passwords: " ).append( user.getContentMode().name() ).append( '\n' );
|
||||
content.append( "##\n" );
|
||||
content.append( "#\n" );
|
||||
|
||||
@@ -25,8 +25,8 @@ import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -56,6 +56,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
int mpVersion = 0, avatar = 0;
|
||||
boolean clearContent = false, headerStarted = false;
|
||||
MPResultType defaultType = null;
|
||||
Instant date = null;
|
||||
|
||||
//noinspection HardcodedLineSeparator
|
||||
for (final String line : CharStreams.readLines( reader ))
|
||||
@@ -66,10 +67,11 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
headerStarted = true;
|
||||
else if ((fullName != null) && (keyID != null))
|
||||
// Ends the header.
|
||||
return new MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(),
|
||||
avatar, defaultType, new Instant( 0 ), false,
|
||||
clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
|
||||
MPMarshalFormat.Flat, file.getParentFile() );
|
||||
return new MPFileUser(
|
||||
fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(), avatar, defaultType,
|
||||
date, false, clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
|
||||
MPMarshalFormat.Flat, file
|
||||
);
|
||||
}
|
||||
|
||||
// Comment.
|
||||
@@ -91,6 +93,8 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
clearContent = "visible".equalsIgnoreCase( value );
|
||||
else if ("Default Type".equalsIgnoreCase( name ))
|
||||
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
|
||||
else if ("Date".equalsIgnoreCase( name ))
|
||||
date = MPModelConstants.dateTimeFormatter.parseDateTime( value ).toInstant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,28 +154,32 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
MPFileSite site;
|
||||
switch (importFormat) {
|
||||
case 0:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 5 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
user.getAlgorithm().mpw_default_counter(),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 6 ),
|
||||
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
site = new MPFileSite(
|
||||
user, siteMatcher.group( 5 ),
|
||||
MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
user.getAlgorithm().mpw_default_counter(),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 6 ),
|
||||
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
if (clearContent)
|
||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) );
|
||||
break;
|
||||
|
||||
case 1:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 7 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 8 ),
|
||||
MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null,
|
||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
site = new MPFileSite(
|
||||
user, siteMatcher.group( 7 ),
|
||||
MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
UnsignedInteger.valueOf(
|
||||
colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 8 ),
|
||||
MPResultType.GeneratedName,
|
||||
clearContent? null: siteMatcher.group( 6 ),
|
||||
null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
if (clearContent) {
|
||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
|
||||
site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) );
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.Separators;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.util.*;
|
||||
|
||||
@@ -27,31 +30,59 @@ import java.util.*;
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MPJSONAnyObject.MPJSONEmptyValue.class)
|
||||
class MPJSONAnyObject {
|
||||
public class MPJSONAnyObject {
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
protected static final ObjectMapper objectMapper = new ObjectMapper() {
|
||||
{
|
||||
setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
|
||||
@Override
|
||||
public DefaultPrettyPrinter withSeparators(final Separators separators) {
|
||||
super.withSeparators( separators );
|
||||
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
|
||||
return this;
|
||||
}
|
||||
} );
|
||||
setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE );
|
||||
setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||
}
|
||||
};
|
||||
|
||||
@JsonAnySetter
|
||||
final Map<String, Object> any = new LinkedHashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAny() {
|
||||
public Map<String, Object> any() {
|
||||
return Collections.unmodifiableMap( any );
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <V> V any(final String key) {
|
||||
return (V) any.get( key );
|
||||
}
|
||||
|
||||
@SuppressWarnings("EqualsAndHashcode")
|
||||
public static class MPJSONEmptyValue {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "ChainOfInstanceofChecks", "Contract" })
|
||||
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
|
||||
@SuppressFBWarnings({ "EQ_UNUSUAL", "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", "HE_EQUALS_USE_HASHCODE" })
|
||||
public boolean equals(final Object obj) {
|
||||
return isEmpty( obj );
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "ChainOfInstanceofChecks", "ConstantConditions" })
|
||||
private static boolean isEmpty(final Object obj) {
|
||||
if (obj == null)
|
||||
return true;
|
||||
if (obj instanceof Collection<?>)
|
||||
return ((Collection<?>) obj).isEmpty();
|
||||
if (obj instanceof Map<?, ?>)
|
||||
return ((Map<?, ?>) obj).isEmpty();
|
||||
if (obj instanceof MPJSONFile.Site.Ext)
|
||||
return ((MPJSONAnyObject) obj).any.isEmpty();
|
||||
if (obj instanceof MPJSONAnyObject)
|
||||
return ((MPJSONAnyObject) obj).any.isEmpty() && (objectMapper.valueToTree( obj ).size() == 0);
|
||||
|
||||
return obj == null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,22 +44,6 @@ import org.joda.time.Instant;
|
||||
@SuppressFBWarnings("URF_UNREAD_FIELD")
|
||||
public class MPJSONFile extends MPJSONAnyObject {
|
||||
|
||||
protected static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
@Override
|
||||
public DefaultPrettyPrinter withSeparators(final Separators separators) {
|
||||
super.withSeparators( separators );
|
||||
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
|
||||
return this;
|
||||
}
|
||||
} );
|
||||
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||
}
|
||||
|
||||
MPJSONFile() {
|
||||
}
|
||||
|
||||
@@ -77,10 +61,14 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
user.avatar = modelUser.getAvatar();
|
||||
user.full_name = modelUser.getFullName();
|
||||
user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||
user.hide_passwords = modelUser.isHidePasswords();
|
||||
user.key_id = modelUser.exportKeyID();
|
||||
user.algorithm = modelUser.getAlgorithm().version();
|
||||
user.default_type = modelUser.getDefaultType();
|
||||
user._ext_mpw = new User.Ext() {
|
||||
{
|
||||
default_type = modelUser.getPreferences().getDefaultType();
|
||||
hide_passwords = modelUser.getPreferences().isHidePasswords();
|
||||
}
|
||||
};
|
||||
|
||||
// Section "sites"
|
||||
sites = new LinkedHashMap<>();
|
||||
@@ -131,8 +119,11 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
}
|
||||
} );
|
||||
|
||||
site._ext_mpw = new Site.Ext();
|
||||
site._ext_mpw.url = modelSite.getUrl();
|
||||
site._ext_mpw = new Site.Ext() {
|
||||
{
|
||||
url = modelSite.getUrl();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +132,11 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
|
||||
return new MPFileUser(
|
||||
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
||||
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
||||
(user._ext_mpw != null)? user._ext_mpw.default_type: null,
|
||||
(user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||
user.hide_passwords, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
|
||||
MPMarshalFormat.JSON, file.getParentFile()
|
||||
(user._ext_mpw != null) && user._ext_mpw.hide_passwords,
|
||||
export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
|
||||
MPMarshalFormat.JSON, file
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,16 +195,24 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
|
||||
public static class User extends MPJSONAnyObject {
|
||||
|
||||
int avatar;
|
||||
String full_name;
|
||||
String last_used;
|
||||
boolean hide_passwords;
|
||||
int avatar;
|
||||
String full_name;
|
||||
String last_used;
|
||||
@Nullable
|
||||
String key_id;
|
||||
@Nullable
|
||||
MPAlgorithm.Version algorithm;
|
||||
|
||||
@Nullable
|
||||
MPResultType default_type;
|
||||
Ext _ext_mpw;
|
||||
|
||||
|
||||
public static class Ext extends MPJSONAnyObject {
|
||||
|
||||
@Nullable
|
||||
MPResultType default_type;
|
||||
boolean hide_passwords;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*
|
||||
@@ -72,4 +75,8 @@ public enum MPMarshalFormat {
|
||||
|
||||
@SuppressWarnings("MethodReturnAlwaysConstant")
|
||||
public abstract String fileSuffix();
|
||||
|
||||
public boolean matches(final File file) {
|
||||
return file.getName().endsWith( fileSuffix() );
|
||||
}
|
||||
}
|
||||
|
||||
Submodule public/site updated: d8d510b6be...ebd2c741fd
Reference in New Issue
Block a user