2
0

User config in _ext_mpw, global config.json & residence config.

Moved user preferences (default type & hide passwords) into _ext_mpw.
Fixed an issue with JSON serialization of any values.
Made update check & background residency globally configurable
preferences saved in config.json.
This commit is contained in:
Maarten Billemont
2018-10-15 02:21:30 -04:00
parent 34042e5462
commit 39dacc8e5a
19 changed files with 323 additions and 134 deletions

View File

@@ -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" );
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -201,8 +201,6 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
@Override
protected void onChanged() {
super.onChanged();
if (user instanceof Changeable)
((Changeable) user).setChanged();
}

View File

@@ -214,8 +214,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
@Override
protected void onChanged() {
super.onChanged();
for (final Listener listener : listeners)
listener.onUserUpdated( this );
}

View File

@@ -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 {

View File

@@ -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<>();

View File

@@ -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;
}
}
}

View File

@@ -44,22 +44,6 @@ import org.joda.time.Instant;
@SuppressFBWarnings("URF_UNREAD_FIELD")
public class MPJSONFile extends MPJSONAnyObject {
protected static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
private static final long serialVersionUID = 1;
@Override
public DefaultPrettyPrinter withSeparators(final Separators separators) {
super.withSeparators( separators );
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
return this;
}
} );
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
}
MPJSONFile() {
}
@@ -79,8 +63,12 @@ public class MPJSONFile extends MPJSONAnyObject {
user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
user.key_id = modelUser.exportKeyID();
user.algorithm = modelUser.getAlgorithm().version();
user.default_type = modelUser.getPreferences().getDefaultType();
user.hide_passwords = modelUser.getPreferences().isHidePasswords();
user._ext_mpw = new User.Ext() {
{
default_type = modelUser.getPreferences().getDefaultType();
hide_passwords = modelUser.getPreferences().isHidePasswords();
}
};
// Section "sites"
sites = new LinkedHashMap<>();
@@ -131,8 +119,11 @@ public class MPJSONFile extends MPJSONAnyObject {
}
} );
site._ext_mpw = new Site.Ext();
site._ext_mpw.url = modelSite.getUrl();
site._ext_mpw = new Site.Ext() {
{
url = modelSite.getUrl();
}
};
}
}
@@ -141,9 +132,10 @@ public class MPJSONFile extends MPJSONAnyObject {
return new MPFileUser(
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
(user._ext_mpw != null)? user._ext_mpw.default_type: null,
(user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
user.hide_passwords, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
(user._ext_mpw != null) && user._ext_mpw.hide_passwords,
export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
MPMarshalFormat.JSON, file
);
}
@@ -203,16 +195,24 @@ public class MPJSONFile extends MPJSONAnyObject {
public static class User extends MPJSONAnyObject {
int avatar;
String full_name;
String last_used;
boolean hide_passwords;
int avatar;
String full_name;
String last_used;
@Nullable
String key_id;
@Nullable
MPAlgorithm.Version algorithm;
@Nullable
MPResultType default_type;
Ext _ext_mpw;
public static class Ext extends MPJSONAnyObject {
@Nullable
MPResultType default_type;
boolean hide_passwords;
}
}