Compare commits
26 Commits
2.7-java-3
...
2.7-java-8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af768329a3 | ||
|
|
9a04c28054 | ||
|
|
ec9c55ec4d | ||
|
|
d8a735e1b1 | ||
|
|
a1eee88a54 | ||
|
|
ac5286853a | ||
|
|
39f6893742 | ||
|
|
7bf7b8981c | ||
|
|
09abe21fed | ||
|
|
6fae0fe425 | ||
|
|
0558176847 | ||
|
|
c553201cda | ||
|
|
665be9494b | ||
|
|
5ca81b4aa7 | ||
|
|
3cbb063926 | ||
|
|
d5551c8c8c | ||
|
|
9a40e52d53 | ||
|
|
6f0d768e69 | ||
|
|
40fdc8d248 | ||
|
|
6b9e1b8cb8 | ||
|
|
f41cdb8742 | ||
|
|
10c6d203b8 | ||
|
|
7d1aa9c9f4 | ||
|
|
c26281e3b7 | ||
|
|
f0b1f0c9e0 | ||
|
|
9682efc7c9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ Thumbs.db
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
out
|
||||
|
||||
# Xcode IDE
|
||||
xcuserdata/
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
FROM debian:buster-slim
|
||||
FROM debian:stable-slim
|
||||
|
||||
# For i386
|
||||
#FROM i386/debian:buster-slim
|
||||
#FROM i386/debian:stable-slim
|
||||
#ENTRYPOINT ["linux32", "--"]
|
||||
|
||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
||||
RUN mkdir -p /usr/share/man/man1
|
||||
|
||||
RUN apt-get update && apt-get install openjdk-10-jdk-headless git-core bash libtool automake autoconf make g++
|
||||
RUN apt-get update && apt-get install -y default-jdk-headless git-core bash libtool automake autoconf make g++
|
||||
RUN git clone --depth=3 $(: --shallow-submodules) --recurse-submodules https://gitlab.com/MasterPassword/MasterPassword.git /mpw
|
||||
RUN cd /mpw/gradle && ./gradlew -i clean build
|
||||
|
||||
14
gradle/.idea/misc.xml
generated
14
gradle/.idea/misc.xml
generated
@@ -5,21 +5,31 @@
|
||||
<option name="myDefaultNotNull" value="javax.annotation.Nonnull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="4">
|
||||
<list size="9">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<list size="9">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
<item index="4" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" />
|
||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
|
||||
@@ -2,11 +2,11 @@ To build a release distribution:
|
||||
|
||||
Desktop:
|
||||
|
||||
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle clean masterpassword-gui:shadowJar
|
||||
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_DESKTOP=$(mpw masterpassword-desktop) gradle --no-daemon clean masterpassword-gui:shadowJar
|
||||
|
||||
Android:
|
||||
|
||||
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle clean masterpassword-android:assembleRelease
|
||||
STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle --no-daemon clean masterpassword-android:assembleRelease
|
||||
|
||||
|
||||
Note:
|
||||
|
||||
@@ -2,7 +2,7 @@ allprojects {
|
||||
apply plugin: 'findbugs'
|
||||
|
||||
group = 'com.lyndir.masterpassword'
|
||||
version = '2.7.3'
|
||||
version = '2.7.8'
|
||||
|
||||
tasks.withType( JavaCompile ) {
|
||||
options.encoding = 'UTF-8'
|
||||
|
||||
@@ -36,7 +36,7 @@ _needs() {
|
||||
IFS=: read pkg tools <<< "$spec"
|
||||
IFS=, read -a tools <<< "${tools:-$pkg}"
|
||||
for tool in "${tools[@]}"; do
|
||||
hash "$tool" && continue 2
|
||||
hash "$tool" 2>/dev/null && continue 2
|
||||
done
|
||||
|
||||
echo >&2 "Missing: $pkg. Please install this package."
|
||||
@@ -64,7 +64,7 @@ _initialize_needs() {
|
||||
if [[ $platform = windows ]]; then
|
||||
needs cmd
|
||||
export VSINSTALLDIR="${VSINSTALLDIR:-$(cd "$(cygpath -F 0x002a)/Microsoft Visual Studio"/*/*/Common7/.. && pwd)}"
|
||||
[[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'."; return 1; }
|
||||
[[ -e "$VSINSTALLDIR/Common7/Tools/VsMSBuildCmd.bat" ]] || { echo >&2 "Missing: msbuild. Please install 'Build Tools for Visual Studio'. See https://visualstudio.microsoft.com/downloads/?q=build+tools"; return 1; }
|
||||
else
|
||||
needs libtool:libtoolize,glibtoolize automake autoconf make
|
||||
fi
|
||||
@@ -202,7 +202,7 @@ _target_build() {
|
||||
|
||||
if [[ $platform = windows ]]; then
|
||||
# I cannot for the life of me figure out how to pass this command directly into cmd.
|
||||
printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=ReleaseDLL;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat
|
||||
printf '"%%VSINSTALLDIR%%\Common7\Tools\VsMSBuildCmd.bat" && msbuild /t:Rebuild /p:Configuration=Release;Platform=%s;OutDir=%s' "$arch" "$(cygpath -w "${prefix##$PWD/}/$arch/")" > .build.bat
|
||||
cmd //c .build.bat
|
||||
rm -f .build.bat
|
||||
else
|
||||
@@ -278,7 +278,10 @@ _finalize_merge() {
|
||||
# By default, this will run `make clean`.
|
||||
finalize_clean() { _finalize_clean "$@"; }
|
||||
_finalize_clean() {
|
||||
[[ ! -e Makefile ]] || make -s clean
|
||||
if [[ $platform = windows ]]; then :
|
||||
else
|
||||
[[ ! -e Makefile ]] || make -s clean
|
||||
fi
|
||||
}
|
||||
|
||||
# build <name> [<platform>]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
platform-independent/c/core/lib/windows/x86/mpw.dll
Executable file → Normal file
BIN
platform-independent/c/core/lib/windows/x86/mpw.dll
Executable file → Normal file
Binary file not shown.
Binary file not shown.
@@ -66,6 +66,7 @@ public class MPMasterKey {
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
@Nonnull
|
||||
public byte[] getKeyID(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
@@ -87,6 +88,7 @@ public class MPMasterKey {
|
||||
return !invalidated;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private byte[] masterKey(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||
@@ -109,6 +111,7 @@ public class MPMasterKey {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@@ -141,13 +144,19 @@ public class MPMasterKey {
|
||||
* In the case of {@link MPResultTypeClass#Stateful} types, the result of
|
||||
* {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
|
||||
*
|
||||
* @return {@code null} if the result type is missing a required parameter.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
@Nullable
|
||||
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, @Nullable final String resultParam)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
if ((resultType.getTypeClass() == MPResultTypeClass.Stateful) && (resultParam == null))
|
||||
return null;
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
|
||||
@@ -176,6 +185,7 @@ public class MPMasterKey {
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
@Nonnull
|
||||
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, final String resultParam)
|
||||
|
||||
@@ -41,7 +41,7 @@ public enum MPResultType {
|
||||
/**
|
||||
* 16: pg^VMAUBk5x3p%HP%i4=
|
||||
*/
|
||||
GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols.", //
|
||||
GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols", //
|
||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
|
||||
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
||||
MPResultTypeClass.Template, 0x0 ),
|
||||
@@ -49,7 +49,7 @@ public enum MPResultType {
|
||||
/**
|
||||
* 17: BiroYena8:Kixa
|
||||
*/
|
||||
GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols.", //
|
||||
GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols", //
|
||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
||||
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
||||
@@ -66,7 +66,7 @@ public enum MPResultType {
|
||||
/**
|
||||
* 18: BirSuj0-
|
||||
*/
|
||||
GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols.", //
|
||||
GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols", //
|
||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
|
||||
new MPTemplate( "CvcCvcno" ) ), //
|
||||
MPResultTypeClass.Template, 0x2 ),
|
||||
@@ -74,14 +74,14 @@ public enum MPResultType {
|
||||
/**
|
||||
* 19: Bir8
|
||||
*/
|
||||
GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols.", //
|
||||
GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols", //
|
||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
||||
MPResultTypeClass.Template, 0x3 ),
|
||||
|
||||
/**
|
||||
* 20: pO98MoD0
|
||||
*/
|
||||
GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols.", //
|
||||
GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols", //
|
||||
ImmutableList.of( new MPTemplate( "aaanaaan" ),
|
||||
new MPTemplate( "aannaaan" ),
|
||||
new MPTemplate( "aaannaaa" ) ), //
|
||||
@@ -90,44 +90,44 @@ public enum MPResultType {
|
||||
/**
|
||||
* 21: 2798
|
||||
*/
|
||||
GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers.", //
|
||||
GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers", //
|
||||
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
||||
MPResultTypeClass.Template, 0x5 ),
|
||||
|
||||
/**
|
||||
* 30: birsujano
|
||||
*/
|
||||
GeneratedName( "name", "Name", "birsujano", "9 letter name.", //
|
||||
GeneratedName( "name", "Name", "birsujano", "9 letter name", //
|
||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
||||
MPResultTypeClass.Template, 0xE ),
|
||||
|
||||
/**
|
||||
* 31: bir yennoquce fefi
|
||||
*/
|
||||
GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence.", //
|
||||
GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence", //
|
||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
|
||||
new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
||||
MPResultTypeClass.Template, 0xF ),
|
||||
|
||||
/**
|
||||
* 1056: Custom saved password.
|
||||
* 1056: Custom saved value.
|
||||
*/
|
||||
StoredPersonal( "personal", "Saved Password", null, "AES-encrypted, exportable.", //
|
||||
StoredPersonal( "personal", "Saved", null, "AES-encrypted, exportable", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
|
||||
|
||||
/**
|
||||
* 2081: Custom saved password that should not be exported from the device.
|
||||
* 2081: Custom saved value that should not be exported from the device.
|
||||
*/
|
||||
StoredDevicePrivate( "device", "Private Password", null, "AES-encrypted, not exported.", //
|
||||
StoredDevicePrivate( "device", "Private", null, "AES-encrypted, not exported", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
|
||||
|
||||
/**
|
||||
* 4160: Derive a unique binary key.
|
||||
*/
|
||||
DeriveKey( "key", "Binary Key", null, "Encryption key.", //
|
||||
DeriveKey( "key", "Binary Key", null, "Encryption key", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.io.*;
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -43,71 +43,161 @@ public final class Native {
|
||||
private static final String NATIVES_PATH = "lib";
|
||||
|
||||
@SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" })
|
||||
public static void load(final Class<?> context, final String name) {
|
||||
public static boolean load(final Class<?> context, final String name) {
|
||||
|
||||
// Try to load the library using the native system.
|
||||
try {
|
||||
System.loadLibrary( name );
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
catch (@SuppressWarnings("ErrorNotRethrown") final UnsatisfiedLinkError ignored) {
|
||||
}
|
||||
|
||||
// Try to find and open a stream to the packaged library resource.
|
||||
try {
|
||||
String library = System.mapLibraryName( name );
|
||||
int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR );
|
||||
String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library;
|
||||
String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
|
||||
String libraryResource = getLibraryResource( library );
|
||||
InputStream libraryStream = context.getResourceAsStream( libraryResource );
|
||||
if (libraryStream == null)
|
||||
throw new IllegalStateException(
|
||||
"Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context );
|
||||
String library = System.mapLibraryName( name );
|
||||
int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR );
|
||||
String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library;
|
||||
String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
|
||||
|
||||
// Write the library resource to a temporary file.
|
||||
File libraryFile = File.createTempFile( libraryName, libraryExtension );
|
||||
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
|
||||
@Nullable
|
||||
File libraryFile = null;
|
||||
Set<String> libraryResources = getLibraryResources( library );
|
||||
for (final String libraryResource : libraryResources) {
|
||||
try {
|
||||
libraryFile.deleteOnExit();
|
||||
ByteStreams.copy( libraryStream, libraryFileStream );
|
||||
}
|
||||
finally {
|
||||
libraryFileStream.close();
|
||||
libraryStream.close();
|
||||
}
|
||||
InputStream libraryStream = context.getResourceAsStream( libraryResource );
|
||||
if (libraryStream == null) {
|
||||
logger.dbg( "No resource for library: %s", libraryResource );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load the library from the temporary file.
|
||||
System.load( libraryFile.getAbsolutePath() );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new IllegalStateException( "Couldn't extract library: " + name, e );
|
||||
// Write the library resource to a temporary file.
|
||||
libraryFile = File.createTempFile( libraryName, libraryExtension );
|
||||
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
|
||||
try {
|
||||
libraryFile.deleteOnExit();
|
||||
ByteStreams.copy( libraryStream, libraryFileStream );
|
||||
}
|
||||
finally {
|
||||
libraryFileStream.close();
|
||||
libraryStream.close();
|
||||
}
|
||||
|
||||
// Load the library from the temporary file.
|
||||
System.load( libraryFile.getAbsolutePath() );
|
||||
return true;
|
||||
}
|
||||
catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) {
|
||||
logger.dbg( e, "Couldn't load library: %s", libraryResource );
|
||||
|
||||
if (libraryFile != null)
|
||||
if (libraryFile.exists() && !libraryFile.delete())
|
||||
logger.wrn( "Couldn't clean up library file: %s", libraryFile );
|
||||
libraryFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static String getLibraryResource(final String library) {
|
||||
String system = ifNotNullElse( System.getProperty( "os.name" ), "linux" ).toLowerCase( Locale.ROOT );
|
||||
String architecture = ifNotNullElse( System.getProperty( "os.arch" ), "x86" ).toLowerCase( Locale.ROOT );
|
||||
|
||||
private static Set<String> getLibraryResources(final String library) {
|
||||
// Standardize system naming in accordance with masterpassword-core.
|
||||
if (system.contains( "windows" ))
|
||||
system = "windows";
|
||||
else if (system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" ))
|
||||
system = "macos";
|
||||
else
|
||||
system = "linux";
|
||||
Sys system = Sys.findCurrent();
|
||||
|
||||
// Standardize architecture naming in accordance with masterpassword-core.
|
||||
if (ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture ))
|
||||
architecture = "arm";
|
||||
else if (architecture.startsWith( "arm" ))
|
||||
architecture = "arm64";
|
||||
else if (ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture ))
|
||||
architecture = "x86_64";
|
||||
else
|
||||
architecture = "x86";
|
||||
Collection<Arch> architectures = new LinkedHashSet<>();
|
||||
architectures.add( Arch.findCurrent() );
|
||||
architectures.addAll( Arrays.asList( Arch.values() ) );
|
||||
|
||||
return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library );
|
||||
ImmutableSet.Builder<String> resources = ImmutableSet.builder();
|
||||
for (final Arch arch : architectures)
|
||||
resources.add( Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, arch, library ) );
|
||||
|
||||
return resources.build();
|
||||
}
|
||||
|
||||
private enum Sys implements Predicate<String> {
|
||||
windows {
|
||||
@Override
|
||||
public boolean test(final String system) {
|
||||
return system.contains( "windows" );
|
||||
}
|
||||
},
|
||||
macos {
|
||||
@Override
|
||||
public boolean test(final String system) {
|
||||
return system.contains( "mac os x" ) || system.contains( "darwin" ) || system.contains( "osx" );
|
||||
}
|
||||
},
|
||||
linux {
|
||||
@Override
|
||||
public boolean test(final String system) {
|
||||
return system.contains( "linux" );
|
||||
}
|
||||
};
|
||||
|
||||
@Nonnull
|
||||
public static Sys findCurrent() {
|
||||
return find( System.getProperty( "os.name" ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Sys find(@Nullable String name) {
|
||||
if (name != null) {
|
||||
name = name.toLowerCase( Locale.ROOT );
|
||||
|
||||
for (final Sys sys : values())
|
||||
if (sys.test( name ))
|
||||
return sys;
|
||||
}
|
||||
|
||||
return linux;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum Arch implements Predicate<String> {
|
||||
arm {
|
||||
@Override
|
||||
public boolean test(final String architecture) {
|
||||
return ImmutableList.of( "arm", "arm-v7", "armv7", "arm32" ).contains( architecture );
|
||||
}
|
||||
},
|
||||
arm64 {
|
||||
@Override
|
||||
public boolean test(final String architecture) {
|
||||
return architecture.startsWith( "arm" ) && !arm.test( architecture );
|
||||
}
|
||||
},
|
||||
x86_64 {
|
||||
@Override
|
||||
public boolean test(final String architecture) {
|
||||
return ImmutableList.of( "x86_64", "amd64", "x64", "x86-64" ).contains( architecture );
|
||||
}
|
||||
},
|
||||
x86 {
|
||||
@Override
|
||||
public boolean test(final String architecture) {
|
||||
return ImmutableList.of( "x86", "i386", "i686" ).contains( architecture );
|
||||
}
|
||||
};
|
||||
|
||||
@Nonnull
|
||||
public static Arch findCurrent() {
|
||||
return find( System.getProperty( "os.arch" ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Arch find(@Nullable String name) {
|
||||
if (name != null) {
|
||||
name = name.toLowerCase( Locale.ROOT );
|
||||
|
||||
for (final Arch arch : values())
|
||||
if (arch.test( name ))
|
||||
return arch;
|
||||
}
|
||||
|
||||
return x86;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ public final class Utilities {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String ifNotNullOrEmptyElse(@Nullable final String value, @Nonnull final String emptyValue) {
|
||||
if ((value == null) || value.isEmpty())
|
||||
return emptyValue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T, R> R ifNotNullElse(@Nullable final T value, final Function<T, R> consumer, @Nonnull final R nullValue) {
|
||||
if (value == null)
|
||||
|
||||
@@ -31,7 +31,7 @@ shadowJar {
|
||||
storepass: System.getenv( 'STORE_PW' ),
|
||||
keypass: System.getenv( 'KEY_PW_DESKTOP' ),
|
||||
preservelastmodified: 'true',
|
||||
destdir: '.' )
|
||||
signedJar: "${rootDir}/../public/site/${project.name}-${project.version}.jar" )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
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;
|
||||
import com.tulskiy.keymaster.common.Provider;
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
|
||||
Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> open() );
|
||||
}
|
||||
|
||||
public void open() {
|
||||
Res.ui( () -> {
|
||||
frame.setAlwaysOnTop( true );
|
||||
frame.setVisible( true );
|
||||
frame.setExtendedState( Frame.NORMAL );
|
||||
frame.setAlwaysOnTop( false );
|
||||
Platform.get().requestForeground();
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ import com.lyndir.masterpassword.model.MPModelConstants;
|
||||
* @author lhunath, 2014-08-31
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class Config {
|
||||
public class MPConfig {
|
||||
|
||||
private static final Config instance = new Config();
|
||||
private static final MPConfig instance = new MPConfig();
|
||||
|
||||
public static Config get() {
|
||||
public static MPConfig get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,11 @@ import com.google.common.base.Charsets;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.*;
|
||||
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.tulskiy.keymaster.common.Provider;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.*;
|
||||
@@ -50,18 +53,21 @@ public final class MasterPassword {
|
||||
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Nullable
|
||||
private MPUser<?> activeUser;
|
||||
private MasterPasswordFrame frame;
|
||||
@Nullable
|
||||
private MPUser<?> activeUser;
|
||||
|
||||
public static MasterPassword get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean addListener(final Listener listener) {
|
||||
return listeners.add( listener );
|
||||
public void addListener(final Listener listener) {
|
||||
if (listeners.add( listener ))
|
||||
listener.onUserSelected( activeUser );
|
||||
}
|
||||
|
||||
public boolean removeListener(final Listener listener) {
|
||||
return listeners.remove( listener );
|
||||
public void removeListener(final Listener listener) {
|
||||
listeners.remove( listener );
|
||||
}
|
||||
|
||||
public void activateUser(final MPUser<?> user) {
|
||||
@@ -78,6 +84,19 @@ public final class MasterPassword {
|
||||
return MasterPassword.class.getPackage().getImplementationVersion();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
Res.ui( () -> {
|
||||
if (frame == null)
|
||||
frame = new MasterPasswordFrame();
|
||||
|
||||
frame.setAlwaysOnTop( true );
|
||||
frame.setVisible( true );
|
||||
frame.setExtendedState( Frame.NORMAL );
|
||||
Platform.get().requestForeground();
|
||||
frame.setAlwaysOnTop( false );
|
||||
} );
|
||||
}
|
||||
|
||||
public void checkUpdate() {
|
||||
try {
|
||||
String implementationVersion = version();
|
||||
@@ -115,18 +134,22 @@ public final class MasterPassword {
|
||||
// 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.
|
||||
new GUI().open();
|
||||
get().open();
|
||||
|
||||
// Check online to see if this version has been superseded.
|
||||
if (Config.get().checkForUpdates())
|
||||
if (MPConfig.get().checkForUpdates())
|
||||
get().checkUpdate();
|
||||
}
|
||||
|
||||
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
|
||||
public interface Listener {
|
||||
|
||||
void onUserSelected(@Nullable MPUser<?> user);
|
||||
|
||||
@@ -31,17 +31,7 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPIncognitoQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPIncognitoSite site;
|
||||
|
||||
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite getSite() {
|
||||
return site;
|
||||
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,10 @@ public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQue
|
||||
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
|
||||
super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoQuestion addQuestion(final String keyword) {
|
||||
return addQuestion( new MPIncognitoQuestion( this, keyword, null ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
@@ -38,6 +39,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite addSite(final String siteName) {
|
||||
return addSite( new MPIncognitoSite( this, siteName ) );
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-07-27
|
||||
*/
|
||||
public class MPNewQuestion extends MPBasicQuestion {
|
||||
|
||||
public MPNewQuestion(final MPSite<?> site, final String keyword) {
|
||||
super( site, keyword, site.getAlgorithm().mpw_default_answer_type() );
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import com.lyndir.masterpassword.model.impl.*;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -12,4 +13,10 @@ public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
|
||||
public MPNewSite(final MPUser<?> user, final String siteName) {
|
||||
super( user, siteName );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPQuestion addQuestion(final String keyword) {
|
||||
throw new UnsupportedOperationException( "Cannot add a question to a site that hasn't been created yet." );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.*;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -16,27 +21,25 @@ import javax.swing.event.ListSelectionListener;
|
||||
public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
implements ComboBoxModel<E>, ListSelectionListener, Selectable<E, CollectionListModel<E>> {
|
||||
|
||||
private static final Logger logger = Logger.get( CollectionListModel.class );
|
||||
|
||||
private final List<E> model = new LinkedList<>();
|
||||
@Nullable
|
||||
private E selectedItem;
|
||||
private JList<E> list;
|
||||
@Nullable
|
||||
private E selectedItem;
|
||||
@Nullable
|
||||
private Consumer<E> selectionConsumer;
|
||||
|
||||
@SafeVarargs
|
||||
public static <E> CollectionListModel<E> copy(final E... elements) {
|
||||
return copy( Arrays.asList( elements ) );
|
||||
public CollectionListModel(final E... elements) {
|
||||
this( Arrays.asList( elements ) );
|
||||
}
|
||||
|
||||
public static <E> CollectionListModel<E> copy(final Collection<? extends E> elements) {
|
||||
CollectionListModel<E> model = new CollectionListModel<>();
|
||||
synchronized (model) {
|
||||
model.model.addAll( elements );
|
||||
model.selectedItem = model.getElementAt( 0 );
|
||||
model.fireIntervalAdded( model, 0, model.model.size() );
|
||||
|
||||
return model;
|
||||
}
|
||||
public CollectionListModel(final Collection<? extends E> elements) {
|
||||
model.addAll( elements );
|
||||
selectedItem = getElementAt( 0 );
|
||||
fireIntervalAdded( this, 0, model.size() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,8 +47,8 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
return model.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Override
|
||||
public synchronized E getElementAt(final int index) {
|
||||
return (index < model.size())? model.get( index ): null;
|
||||
}
|
||||
@@ -56,16 +59,11 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
* This operation will mutate the internal model to reflect the given model.
|
||||
* The given model will remain untouched and independent from this object.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "SuspiciousToArrayCall" })
|
||||
public synchronized void set(final Collection<? extends E> elements) {
|
||||
set( (E[]) elements.toArray( new Object[0] ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssignmentToForLoopParameter")
|
||||
public synchronized void set(final E... elements) {
|
||||
@SuppressWarnings({ "Guava", "AssignmentToForLoopParameter" })
|
||||
public synchronized void set(final Iterable<? extends E> elements) {
|
||||
ListIterator<E> oldIt = model.listIterator();
|
||||
for (int from = 0; oldIt.hasNext(); ++from) {
|
||||
int to = Arrays.binarySearch( elements, oldIt.next() );
|
||||
int to = Iterables.indexOf( elements, Predicates.equalTo( oldIt.next() ) );
|
||||
|
||||
if (to != from) {
|
||||
oldIt.remove();
|
||||
@@ -74,33 +72,46 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
}
|
||||
}
|
||||
|
||||
for (int to = 0; to < elements.length; ++to) {
|
||||
E newSite = elements[to];
|
||||
|
||||
int to = 0;
|
||||
for (final E newSite : elements) {
|
||||
if ((to >= model.size()) || !Objects.equals( model.get( to ), newSite )) {
|
||||
model.add( to, newSite );
|
||||
fireIntervalAdded( this, to, to );
|
||||
}
|
||||
|
||||
++to;
|
||||
}
|
||||
|
||||
if ((selectedItem == null) || !model.contains( selectedItem ))
|
||||
setSelectedItem( getElementAt( 0 ) );
|
||||
selectItem( getElementAt( 0 ) );
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final synchronized void set(final E... elements) {
|
||||
set( ImmutableList.copyOf( elements ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "SuspiciousMethodCalls" })
|
||||
public synchronized void setSelectedItem(@Nullable final Object newSelectedItem) {
|
||||
if (!Objects.equals( selectedItem, newSelectedItem )) {
|
||||
selectedItem = (E) newSelectedItem;
|
||||
@Deprecated
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized void setSelectedItem(@Nullable final Object/* E */ newSelectedItem) {
|
||||
selectItem( (E) newSelectedItem );
|
||||
}
|
||||
|
||||
fireContentsChanged( this, -1, -1 );
|
||||
//noinspection ObjectEquality
|
||||
if ((list != null) && (list.getModel() == this))
|
||||
list.setSelectedValue( selectedItem, true );
|
||||
public synchronized CollectionListModel<E> selectItem(@Nullable final E newSelectedItem) {
|
||||
if (Objects.equals( selectedItem, newSelectedItem ))
|
||||
return this;
|
||||
|
||||
if (selectionConsumer != null)
|
||||
selectionConsumer.accept( selectedItem );
|
||||
}
|
||||
selectedItem = newSelectedItem;
|
||||
|
||||
fireContentsChanged( this, -1, -1 );
|
||||
//noinspection ObjectEquality
|
||||
if ((list != null) && (list.getModel() == this))
|
||||
list.setSelectedValue( selectedItem, true );
|
||||
|
||||
if (selectionConsumer != null)
|
||||
selectionConsumer.accept( selectedItem );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -131,7 +142,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
@Override
|
||||
public synchronized CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
|
||||
this.selectionConsumer = null;
|
||||
setSelectedItem( selectedItem );
|
||||
selectItem( selectedItem );
|
||||
|
||||
return selection( selectionConsumer );
|
||||
}
|
||||
@@ -139,7 +150,7 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
@Override
|
||||
public synchronized void valueChanged(final ListSelectionEvent event) {
|
||||
//noinspection ObjectEquality
|
||||
if (!event.getValueIsAdjusting() && (event.getSource() == list) && (list.getModel() == this)) {
|
||||
if (!event.getValueIsAdjusting() && (event.getSource() == list) && (checkNotNull( list ).getModel() == this)) {
|
||||
selectedItem = list.getSelectedValue();
|
||||
|
||||
if (selectionConsumer != null)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -32,8 +33,8 @@ import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.text.DefaultFormatterFactory;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.text.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
@@ -100,6 +101,7 @@ public abstract class Components {
|
||||
|
||||
public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
|
||||
JDialog dialog = pane.createDialog( owner, title );
|
||||
dialog.setMinimumSize( new Dimension( 520, 0 ) );
|
||||
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
|
||||
showDialog( dialog );
|
||||
|
||||
@@ -111,8 +113,13 @@ public abstract class Components {
|
||||
if (options == null)
|
||||
return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
|
||||
|
||||
int option = Arrays.binarySearch( options, selectedValue );
|
||||
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
|
||||
try {
|
||||
int option = Arrays.binarySearch( options, selectedValue );
|
||||
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
|
||||
}
|
||||
catch (final ClassCastException ignored) {
|
||||
return JOptionPane.CLOSED_OPTION;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -143,7 +150,6 @@ public abstract class Components {
|
||||
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
||||
dialog.setMinimumSize( new Dimension( 320, 0 ) );
|
||||
dialog.setLocationRelativeTo( owner );
|
||||
dialog.setLocationByPlatform( true );
|
||||
dialog.setContentPane( content );
|
||||
|
||||
return showDialog( dialog );
|
||||
@@ -155,13 +161,18 @@ public abstract class Components {
|
||||
dialog.getRootPane().putClientProperty( "Window.style", "small" );
|
||||
dialog.pack();
|
||||
|
||||
dialog.setLocationByPlatform( true );
|
||||
dialog.setVisible( true );
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static JTextField textField() {
|
||||
return new JTextField() {
|
||||
return textField( null );
|
||||
}
|
||||
|
||||
public static JTextField textField(@Nullable final Document document) {
|
||||
return new JTextField( document, null, 0 ) {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
@@ -171,41 +182,30 @@ public abstract class Components {
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> selection) {
|
||||
return new JTextField( text ) {
|
||||
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> change) {
|
||||
return textField( new DocumentModel( new PlainDocument() ).selection( text, change ).getDocument() );
|
||||
}
|
||||
|
||||
public static JTextArea textArea() {
|
||||
return new JTextArea() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
if (selection != null)
|
||||
getDocument().addDocumentListener( new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
}
|
||||
} );
|
||||
setLineWrap( true );
|
||||
setRows( 3 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -228,20 +228,27 @@ public abstract class Components {
|
||||
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
|
||||
return new JList<E>( model ) {
|
||||
{
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||
setCellRenderer( new DefaultListCellRenderer() {
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
|
||||
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
|
||||
final boolean isSelected, final boolean cellHasFocus) {
|
||||
String label = valueTransformer.apply( (E) value );
|
||||
super.getListCellRendererComponent(
|
||||
list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus );
|
||||
list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
|
||||
setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) );
|
||||
|
||||
return this;
|
||||
}
|
||||
} );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
Dimension cellSize = getCellRenderer().getListCellRendererComponent( this, null, 0, false, false ).getPreferredSize();
|
||||
setFixedCellWidth( cellSize.width );
|
||||
setFixedCellHeight( cellSize.height );
|
||||
|
||||
if (model instanceof CollectionListModel)
|
||||
((CollectionListModel<E>) model).registerList( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -297,7 +304,6 @@ public abstract class Components {
|
||||
public static JButton button(final Action action) {
|
||||
return new JButton( action ) {
|
||||
{
|
||||
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
if (getText() == null) {
|
||||
@@ -374,7 +380,7 @@ public abstract class Components {
|
||||
public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) {
|
||||
return new JLabel( heading, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.fonts().controlFont( TEXT_SIZE_HEADING ).deriveFont( Font.BOLD ) );
|
||||
setFont( getFont().deriveFont( Font.BOLD, TEXT_SIZE_HEADING ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
}
|
||||
|
||||
@@ -409,7 +415,6 @@ public abstract class Components {
|
||||
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
|
||||
return new JLabel( label, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
}
|
||||
|
||||
@@ -423,7 +428,6 @@ public abstract class Components {
|
||||
public static JCheckBox checkBox(final String label) {
|
||||
return new JCheckBox( label ) {
|
||||
{
|
||||
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
}
|
||||
@@ -437,17 +441,17 @@ public abstract class Components {
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer,
|
||||
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
|
||||
@Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
|
||||
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
|
||||
@@ -460,8 +464,9 @@ public abstract class Components {
|
||||
@SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" })
|
||||
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
|
||||
final boolean isSelected, final boolean cellHasFocus) {
|
||||
String label = valueTransformer.apply( (E) value );
|
||||
super.getListCellRendererComponent(
|
||||
list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus );
|
||||
list, Strings.isNullOrEmpty( label )? " ": label, index, isSelected, cellHasFocus );
|
||||
setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) );
|
||||
|
||||
return this;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-08-23
|
||||
*/
|
||||
public class ConsumingTrigger<T> implements Consumer<T> {
|
||||
|
||||
private final Runnable trigger;
|
||||
|
||||
@Nullable
|
||||
private T value;
|
||||
|
||||
public ConsumingTrigger(final Runnable trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final T t) {
|
||||
value = t;
|
||||
|
||||
trigger.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-08-24
|
||||
*/
|
||||
public class DocumentModel implements Selectable<String, DocumentModel> {
|
||||
|
||||
private static final Logger logger = Logger.get( DocumentModel.class );
|
||||
|
||||
private final Document document;
|
||||
|
||||
@Nullable
|
||||
private DocumentListener documentListener;
|
||||
|
||||
public DocumentModel(final Document document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getText() {
|
||||
try {
|
||||
return (document.getLength() > 0)? document.getText( 0, document.getLength() ): null;
|
||||
}
|
||||
catch (final BadLocationException e) {
|
||||
logger.wrn( "While getting text for model", e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(@Nullable final String text) {
|
||||
try {
|
||||
if (document.getLength() > 0)
|
||||
document.remove( 0, document.getLength() );
|
||||
|
||||
if (text != null)
|
||||
document.insertString( 0, text, null );
|
||||
}
|
||||
catch (final BadLocationException e) {
|
||||
logger.err( "While setting text for model", e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentModel selection(@Nullable final Consumer<String> selectionConsumer) {
|
||||
if (documentListener != null)
|
||||
document.removeDocumentListener( documentListener );
|
||||
|
||||
if (selectionConsumer != null)
|
||||
document.addDocumentListener( documentListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
private void trigger() {
|
||||
selectionConsumer.accept( getText() );
|
||||
}
|
||||
} );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentModel selection(@Nullable final String selectedItem, @Nullable final Consumer<String> selectionConsumer) {
|
||||
setText( selectedItem );
|
||||
selection( selectionConsumer );
|
||||
|
||||
if (selectionConsumer != null)
|
||||
selectionConsumer.accept( selectedItem );
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,13 @@ import com.google.common.io.Resources;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.SwingExecutorService;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.joda.time.*;
|
||||
@@ -80,6 +81,15 @@ public abstract class Res {
|
||||
ui( true, job );
|
||||
}
|
||||
|
||||
public static <V> void ui(final ListenableFuture<V> future, final Consumer<V> job) {
|
||||
Futures.addCallback( future, new FailableCallback<V>( logger ) {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final V result) {
|
||||
job.accept( result );
|
||||
}
|
||||
}, uiExecutor() );
|
||||
}
|
||||
|
||||
public static void ui(final boolean immediate, final Runnable job) {
|
||||
uiExecutor( immediate ).execute( job );
|
||||
}
|
||||
@@ -150,6 +160,14 @@ public abstract class Res {
|
||||
return icon( "media/icon_settings.png" );
|
||||
}
|
||||
|
||||
public Icon edit() {
|
||||
return icon( "media/icon_edit.png" );
|
||||
}
|
||||
|
||||
public Icon key() {
|
||||
return icon( "media/icon_key.png" );
|
||||
}
|
||||
|
||||
public Icon avatar(final int index) {
|
||||
return icon( strf( "media/avatar-%d.png", index % avatars() ) );
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ public interface Selectable<E, T> {
|
||||
|
||||
T selection(@Nullable Consumer<E> selectionConsumer);
|
||||
|
||||
T selection(E selectedItem, @Nullable Consumer<E> selectionConsumer);
|
||||
T selection(@Nullable E selectedItem, @Nullable Consumer<E> selectionConsumer);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
@@ -29,9 +29,9 @@ public class SwingExecutorService extends AbstractExecutorService {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
|
||||
synchronized (pendingCommands) {
|
||||
shutdown = true;
|
||||
|
||||
if (pendingCommands.isEmpty())
|
||||
terminated.add( true );
|
||||
}
|
||||
@@ -49,7 +49,9 @@ public class SwingExecutorService extends AbstractExecutorService {
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
synchronized (pendingCommands) {
|
||||
return shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,10 +67,10 @@ public class SwingExecutorService extends AbstractExecutorService {
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull final Runnable command) {
|
||||
if (shutdown)
|
||||
throw new RejectedExecutionException( "Executor is shut down." );
|
||||
|
||||
synchronized (pendingCommands) {
|
||||
if (shutdown)
|
||||
throw new RejectedExecutionException( "Executor is shut down." );
|
||||
|
||||
pendingCommands.add( command );
|
||||
}
|
||||
|
||||
@@ -109,13 +109,14 @@ public class UnsignedIntegerModel extends SpinnerNumberModel implements Selectab
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedIntegerModel selection(final UnsignedInteger selectedItem, @Nullable final Consumer<UnsignedInteger> selectionConsumer) {
|
||||
public UnsignedIntegerModel selection(@Nullable final UnsignedInteger selectedItem,
|
||||
@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
|
||||
if (changeListener != null) {
|
||||
removeChangeListener( changeListener );
|
||||
changeListener = null;
|
||||
}
|
||||
|
||||
setValue( selectedItem );
|
||||
setValue( (selectedItem != null)? selectedItem: getMinimum() );
|
||||
return selection( selectionConsumer );
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||
import java.awt.*;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -25,9 +23,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
"Click to change the user's avatar." );
|
||||
|
||||
private final CollectionListModel<MPUser<?>> usersModel =
|
||||
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||
private final JComboBox<? extends MPUser<?>> userField =
|
||||
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
|
||||
new CollectionListModel<MPUser<?>>( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||
|
||||
protected FilesPanel() {
|
||||
setOpaque( false );
|
||||
@@ -46,7 +42,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
add( Components.strut( Components.margin() ) );
|
||||
|
||||
// User Selection
|
||||
add( userField );
|
||||
add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) );
|
||||
|
||||
MPFileUserManager.get().addListener( this );
|
||||
MasterPassword.get().addListener( this );
|
||||
@@ -68,7 +64,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
|
||||
@Override
|
||||
public void onUserSelected(@Nullable final MPUser<?> user) {
|
||||
usersModel.setSelectedItem( user );
|
||||
usersModel.selectItem( user );
|
||||
avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@ 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;
|
||||
|
||||
@@ -21,26 +19,24 @@ public class MasterPasswordFrame extends JFrame {
|
||||
|
||||
private static final Logger logger = Logger.get( MasterPasswordFrame.class );
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Components.GradientPanel root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS );
|
||||
private final FilesPanel filesPanel = new FilesPanel();
|
||||
private final JPanel userPanel = Components.panel( new BorderLayout( 0, 0 ) );
|
||||
private final UserContentPanel userContent = new UserContentPanel();
|
||||
private final UserContentPanel userContent;
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public MasterPasswordFrame() {
|
||||
super( "Master Password" );
|
||||
|
||||
setContentPane( root );
|
||||
root.add( filesPanel );
|
||||
root.add( Components.strut() );
|
||||
root.add( userPanel );
|
||||
JPanel root, userPanel;
|
||||
setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) );
|
||||
|
||||
root.add( new FilesPanel() );
|
||||
root.add( Components.strut() );
|
||||
root.add( userPanel = Components.panel( new BorderLayout( 0, 0 ) ) );
|
||||
|
||||
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
|
||||
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
|
||||
userPanel.add( Components.borderPanel(
|
||||
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
||||
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
||||
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent = new UserContentPanel() ), BorderLayout.CENTER );
|
||||
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
|
||||
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
|
||||
|
||||
addComponentListener( new ComponentHandler() );
|
||||
setPreferredSize( new Dimension( 800, 560 ) );
|
||||
|
||||
@@ -5,13 +5,13 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
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.model.MPIncognitoUser;
|
||||
import com.lyndir.masterpassword.gui.model.MPNewSite;
|
||||
import com.lyndir.masterpassword.gui.model.*;
|
||||
import com.lyndir.masterpassword.gui.util.*;
|
||||
import com.lyndir.masterpassword.gui.util.Platform;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
@@ -28,12 +28,12 @@ import java.util.*;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.PlainDocument;
|
||||
|
||||
|
||||
/**
|
||||
@@ -42,9 +42,11 @@ import javax.swing.event.DocumentListener;
|
||||
@SuppressWarnings("SerializableStoresNonSerializable")
|
||||
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 static final Random random = new Random();
|
||||
private static final int SIZE_RESULT = 48;
|
||||
private static final Logger logger = Logger.get( UserContentPanel.class );
|
||||
private static final JButton iconButton = Components.button( Res.icons().user(), null, null );
|
||||
private static final KeyStroke copyLoginKeyStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK );
|
||||
|
||||
private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
|
||||
"Add a new user to Master Password." );
|
||||
@@ -290,9 +292,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
|
||||
"Change the master password for this user." );
|
||||
|
||||
private final JPasswordField masterPasswordField = Components.passwordField();
|
||||
private final JLabel errorLabel = Components.label();
|
||||
private final JLabel identiconLabel = Components.label( SwingConstants.CENTER );
|
||||
private final JPasswordField masterPasswordField;
|
||||
private final JLabel errorLabel;
|
||||
private final JLabel identiconLabel;
|
||||
|
||||
private Future<?> identiconJob;
|
||||
|
||||
@@ -312,16 +314,16 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||
add( Components.strut() );
|
||||
|
||||
add( identiconLabel );
|
||||
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
|
||||
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
|
||||
add( Box.createGlue() );
|
||||
|
||||
add( Components.label( "Master Password:" ) );
|
||||
add( Components.strut() );
|
||||
add( masterPasswordField );
|
||||
add( masterPasswordField = Components.passwordField() );
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( errorLabel );
|
||||
add( errorLabel = Components.label() );
|
||||
errorLabel.setForeground( Res.colors().errorFg() );
|
||||
add( Box.createGlue() );
|
||||
}
|
||||
@@ -458,9 +460,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
|
||||
private final class AuthenticatedUserPanel extends JPanel implements KeyListener, MPUser.Listener {
|
||||
|
||||
public static final int SIZE_RESULT = 48;
|
||||
private final class AuthenticatedUserPanel extends JPanel implements KeyListener, MPUser.Listener, KeyEventDispatcher {
|
||||
|
||||
private final JButton userButton = Components.button( Res.icons().user(), event -> showUserPreferences(),
|
||||
"Show user preferences." );
|
||||
@@ -468,22 +468,28 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
"Sign out and lock user." );
|
||||
private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(),
|
||||
"Show site settings." );
|
||||
private final JButton questionsButton = Components.button( Res.icons().question(), null,
|
||||
private final JButton questionsButton = Components.button( Res.icons().question(), event -> showSiteQuestions(),
|
||||
"Show site recovery questions." );
|
||||
private final JButton editButton = Components.button( Res.icons().edit(), event -> showSiteValues(),
|
||||
"Set/save personal password/login." );
|
||||
private final JButton keyButton = Components.button( Res.icons().key(), event -> showSiteKeys(),
|
||||
"Cryptographic site keys." );
|
||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
|
||||
"Delete the site from the user." );
|
||||
|
||||
@Nonnull
|
||||
private final MPUser<?> user;
|
||||
private final JLabel passwordLabel = Components.label( SwingConstants.CENTER );
|
||||
private final JLabel passwordField = Components.heading( SwingConstants.CENTER );
|
||||
private final JLabel queryLabel = Components.label();
|
||||
private final JTextField queryField = Components.textField( null, this::updateSites );
|
||||
private final CollectionListModel<MPSite<?>> sitesModel =
|
||||
new CollectionListModel<MPSite<?>>().selection( this::showSiteResult );
|
||||
private final JList<MPSite<?>> sitesList =
|
||||
Components.list( sitesModel, this::getSiteDescription );
|
||||
private final MPUser<?> user;
|
||||
private final JLabel resultLabel;
|
||||
private final JLabel resultField;
|
||||
private final JLabel answerLabel;
|
||||
private final JLabel answerField;
|
||||
private final JLabel queryLabel;
|
||||
private final JTextField queryField;
|
||||
private final CollectionListModel<MPQuery.Result<? extends MPSite<?>>> sitesModel;
|
||||
private final CollectionListModel<MPQuery.Result<? extends MPQuestion>> questionsModel;
|
||||
private final JList<MPQuery.Result<? extends MPSite<?>>> sitesList;
|
||||
|
||||
private boolean showLogin;
|
||||
private Future<?> updateSitesJob;
|
||||
|
||||
private AuthenticatedUserPanel(@Nonnull final MPUser<?> user) {
|
||||
@@ -499,36 +505,65 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
|
||||
siteToolbar.add( settingsButton );
|
||||
siteToolbar.add( questionsButton );
|
||||
siteToolbar.add( editButton );
|
||||
siteToolbar.add( keyButton );
|
||||
siteToolbar.add( deleteButton );
|
||||
settingsButton.setEnabled( false );
|
||||
questionsButton.setEnabled( false );
|
||||
editButton.setEnabled( false );
|
||||
keyButton.setEnabled( false );
|
||||
deleteButton.setEnabled( false );
|
||||
|
||||
answerLabel = Components.label( "Answer:" );
|
||||
answerField = Components.heading( SwingConstants.CENTER );
|
||||
answerField.setForeground( Res.colors().highlightFg() );
|
||||
answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
||||
questionsModel = new CollectionListModel<MPQuery.Result<? extends MPQuestion>>().selection( this::showQuestionItem );
|
||||
|
||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||
|
||||
add( passwordLabel );
|
||||
add( passwordField );
|
||||
passwordField.setForeground( Res.colors().highlightFg() );
|
||||
passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
||||
add( resultLabel = Components.label( SwingConstants.CENTER ) );
|
||||
add( resultField = Components.heading( SwingConstants.CENTER ) );
|
||||
resultField.setForeground( Res.colors().highlightFg() );
|
||||
resultField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
||||
add( Box.createGlue() );
|
||||
add( Components.strut() );
|
||||
|
||||
add( queryLabel );
|
||||
add( queryLabel = Components.label() );
|
||||
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
|
||||
add( queryField );
|
||||
add( queryField = Components.textField( null, this::updateSites ) );
|
||||
queryField.putClientProperty( "JTextField.variant", "search" );
|
||||
queryField.addActionListener( event -> useSite() );
|
||||
queryField.addActionListener( this::useSite );
|
||||
queryField.getInputMap().put( copyLoginKeyStroke, JTextField.notifyAction );
|
||||
queryField.addKeyListener( this );
|
||||
queryField.requestFocusInWindow();
|
||||
add( Components.strut() );
|
||||
add( Components.scrollPane( sitesList ) );
|
||||
sitesModel.registerList( sitesList );
|
||||
add( Box.createGlue() );
|
||||
|
||||
add( Components.scrollPane( sitesList = Components.list(
|
||||
sitesModel = new CollectionListModel<MPQuery.Result<? extends MPSite<?>>>().selection( this::showSiteItem ),
|
||||
this::getSiteDescription ) ) );
|
||||
sitesList.registerKeyboardAction( this::useSite, KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
|
||||
JComponent.WHEN_FOCUSED );
|
||||
sitesList.registerKeyboardAction( this::useSite, KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK ),
|
||||
JComponent.WHEN_FOCUSED );
|
||||
add( Components.strut() );
|
||||
|
||||
add( Components.label( strf(
|
||||
"Press %s to copy password, %s+%s to copy login name.",
|
||||
KeyEvent.getKeyText( KeyEvent.VK_ENTER ),
|
||||
InputEvent.getModifiersExText( copyLoginKeyStroke.getModifiers() ),
|
||||
KeyEvent.getKeyText( copyLoginKeyStroke.getKeyCode() ) ) ) );
|
||||
|
||||
addHierarchyListener( e -> {
|
||||
if (null != SwingUtilities.windowForComponent( this ))
|
||||
user.addListener( this );
|
||||
else
|
||||
user.removeListener( this );
|
||||
if (HierarchyEvent.DISPLAYABILITY_CHANGED == (e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED)) {
|
||||
if (null != SwingUtilities.windowForComponent( this )) {
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher( this );
|
||||
user.addListener( this );
|
||||
} else {
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher( this );
|
||||
user.removeListener( this );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -556,7 +591,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
|
||||
public void showSiteSettings() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
MPSite<?> site = getSite();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
@@ -572,12 +607,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
Components.strut() );
|
||||
|
||||
components.add( Components.label( "Password Type:" ),
|
||||
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
|
||||
Components.comboBox( MPResultType.values(), type -> getTypeDescription(
|
||||
type, user.getDefaultType(), user.getAlgorithm().mpw_default_result_type() ),
|
||||
site.getResultType(), site::setResultType ),
|
||||
Components.strut() );
|
||||
|
||||
components.add( Components.label( "Login Type:" ),
|
||||
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
|
||||
Components.comboBox( MPResultType.values(), type -> getTypeDescription(
|
||||
type, user.getAlgorithm().mpw_default_login_type() ),
|
||||
site.getLoginType(), site::setLoginType ),
|
||||
Components.strut() );
|
||||
|
||||
@@ -587,12 +624,181 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
Components.textField( fileSite.getUrl(), fileSite::setUrl ),
|
||||
Components.strut() );
|
||||
|
||||
Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
|
||||
Components.showDialog( this, strf( "Settings for %s", site.getSiteName() ), new JOptionPane( Components.panel(
|
||||
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
|
||||
}
|
||||
|
||||
private String getTypeDescription(final MPResultType type, final MPResultType... defaults) {
|
||||
boolean isDefault = false;
|
||||
for (final MPResultType d : defaults)
|
||||
if (isDefault = type == d)
|
||||
break;
|
||||
|
||||
return strf( "<html>%s%s%s, %s", isDefault? "<b>": "", type.getLongName(), isDefault? "</b>": "", type.getDescription() );
|
||||
}
|
||||
|
||||
public void showSiteQuestions() {
|
||||
MPSite<?> site = getSite();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
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 ) );
|
||||
if (questionItems.stream().noneMatch( MPQuery.Result::isExact ))
|
||||
questionItems.add( MPQuery.Result.allOf( new MPNewQuestion( site, query.getQuery() ), query.getQuery() ) );
|
||||
|
||||
Res.ui( () -> questionsModel.set( questionItems ) );
|
||||
} ) );
|
||||
queryField.putClientProperty( "JTextField.variant", "search" );
|
||||
queryField.addActionListener( this::useQuestion );
|
||||
queryField.addKeyListener( new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(final KeyEvent event) {
|
||||
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
|
||||
questionsList.dispatchEvent( event );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(final KeyEvent event) {
|
||||
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
|
||||
questionsList.dispatchEvent( event );
|
||||
}
|
||||
} );
|
||||
|
||||
Components.showDialog( this, strf( "Recovery answers for %s", site.getSiteName() ), new JOptionPane( Components.panel(
|
||||
BoxLayout.PAGE_AXIS,
|
||||
Components.label( "Security Question Keyword:" ), queryField,
|
||||
Components.strut(),
|
||||
answerLabel, answerField,
|
||||
Components.strut(),
|
||||
Components.scrollPane( questionsList ) ) ) {
|
||||
@Override
|
||||
public void selectInitialValue() {
|
||||
queryField.requestFocusInWindow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public void showSiteValues() {
|
||||
MPSite<?> site = getSite();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
JTextField passwordField = Components.textField( site.getResult(), null );
|
||||
JTextField loginField = Components.textField( site.getLogin(), null );
|
||||
passwordField.setEditable( site.getResultType().getTypeClass() == MPResultTypeClass.Stateful );
|
||||
loginField.setEditable( site.getLoginType().getTypeClass() == MPResultTypeClass.Stateful );
|
||||
|
||||
if (JOptionPane.OK_OPTION == Components.showDialog( this, site.getSiteName(), new JOptionPane(
|
||||
Components.panel(
|
||||
BoxLayout.PAGE_AXIS,
|
||||
Components.label( strf( "<html>Site Login (currently set to: <b>%s</b>):",
|
||||
getTypeDescription( site.getLoginType() ) ) ),
|
||||
loginField,
|
||||
Components.strut(),
|
||||
Components.label( strf( "<html>Site Password (currently set to: <b>%s</b>):",
|
||||
getTypeDescription( site.getResultType() ) ) ),
|
||||
passwordField,
|
||||
Components.strut(),
|
||||
Components.label( "<html>To save a personal value in these fields,\n" +
|
||||
"change the type to <b>Saved</b> in the site's settings." ) ),
|
||||
JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) {
|
||||
@Override
|
||||
public void selectInitialValue() {
|
||||
passwordField.requestFocusInWindow();
|
||||
}
|
||||
} )) {
|
||||
if (site instanceof MPFileSite) {
|
||||
MPFileSite fileSite = (MPFileSite) site;
|
||||
|
||||
if (site.getResultType().getTypeClass() == MPResultTypeClass.Stateful)
|
||||
fileSite.setSitePassword( site.getResultType(), passwordField.getText() );
|
||||
if (site.getLoginType().getTypeClass() == MPResultTypeClass.Stateful)
|
||||
fileSite.setLoginName( site.getLoginType(), loginField.getText() );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.err( e, "While computing site edit results." );
|
||||
}
|
||||
}
|
||||
|
||||
public void showSiteKeys() {
|
||||
MPSite<?> site = getSite();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
JTextArea resultField = Components.textArea();
|
||||
resultField.setEnabled( false );
|
||||
|
||||
CollectionListModel<MPKeyPurpose> purposeModel = new CollectionListModel<>( MPKeyPurpose.values() );
|
||||
DocumentModel contextModel = new DocumentModel( new PlainDocument() );
|
||||
UnsignedIntegerModel counterModel = new UnsignedIntegerModel( UnsignedInteger.ONE );
|
||||
CollectionListModel<MPResultType> typeModel = new CollectionListModel<>( MPResultType.values() );
|
||||
DocumentModel stateModel = new DocumentModel( new PlainDocument() );
|
||||
|
||||
Runnable trigger = () -> Res.job( () -> {
|
||||
try {
|
||||
MPKeyPurpose purpose = purposeModel.getSelectedItem();
|
||||
MPResultType type = typeModel.getSelectedItem();
|
||||
|
||||
String result = ((purpose == null) || (type == null))? null:
|
||||
site.getResult( purpose, contextModel.getText(), counterModel.getNumber(), type, stateModel.getText() );
|
||||
|
||||
Res.ui( () -> resultField.setText( result ) );
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.err( e, "While computing site edit results." );
|
||||
}
|
||||
} );
|
||||
|
||||
purposeModel.selection( MPKeyPurpose.Authentication, p -> trigger.run() );
|
||||
contextModel.selection( c -> trigger.run() );
|
||||
counterModel.selection( c -> trigger.run() );
|
||||
typeModel.selection( MPResultType.DeriveKey, t -> {
|
||||
switch (t) {
|
||||
case DeriveKey:
|
||||
stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
|
||||
break;
|
||||
|
||||
default:
|
||||
stateModel.setText( null );
|
||||
}
|
||||
|
||||
trigger.run();
|
||||
} );
|
||||
stateModel.selection( c -> trigger.run() );
|
||||
|
||||
if (JOptionPane.OK_OPTION == Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
|
||||
BoxLayout.PAGE_AXIS,
|
||||
Components.heading( "Key Calculator" ),
|
||||
Components.label( "Purpose:" ),
|
||||
Components.comboBox( purposeModel, MPKeyPurpose::getShortName ),
|
||||
Components.strut(),
|
||||
Components.label( "Context:" ),
|
||||
Components.textField( contextModel.getDocument() ),
|
||||
Components.label( "Counter:" ),
|
||||
Components.spinner( counterModel ),
|
||||
Components.label( "Type:" ),
|
||||
Components.comboBox( typeModel, this::getTypeDescription ),
|
||||
Components.label( "State:" ),
|
||||
Components.scrollPane( Components.textField( stateModel.getDocument() ) ),
|
||||
Components.strut(),
|
||||
resultField ) ) {
|
||||
{
|
||||
setOptions( new Object[]{ "Copy", "Cancel" } );
|
||||
setInitialValue( getOptions()[0] );
|
||||
}
|
||||
} ))
|
||||
copyResult( resultField.getText() );
|
||||
}
|
||||
|
||||
public void deleteSite() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
MPSite<?> site = getSite();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
@@ -602,96 +808,188 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
user.deleteSite( site );
|
||||
}
|
||||
|
||||
private String getSiteDescription(@Nonnull final MPSite<?> site) {
|
||||
private String getSiteDescription(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
|
||||
MPSite<?> site = (item != null)? item.getOption(): null;
|
||||
if (site == null)
|
||||
return " ";
|
||||
if (site instanceof MPNewSite)
|
||||
return strf( "<html><strong>%s</strong> <Add new site></html>", queryField.getText() );
|
||||
return strf( "<html><strong>%s</strong> <Add new site></html>", item.getKeyAsHTML() );
|
||||
|
||||
ImmutableList.Builder<Object> parameters = ImmutableList.builder();
|
||||
try {
|
||||
MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null;
|
||||
if (fileSite != null)
|
||||
parameters.add( Res.format( fileSite.getLastUsed() ) );
|
||||
parameters.add( site.getAlgorithm().version() );
|
||||
parameters.add( strf( "#%d", site.getCounter().longValue() ) );
|
||||
parameters.add( strf( "<em>%s</em>", site.getLogin() ) );
|
||||
if ((fileSite != null) && (fileSite.getUrl() != null))
|
||||
parameters.add( fileSite.getUrl() );
|
||||
}
|
||||
catch (final MPAlgorithmException | MPKeyUnavailableException e) {
|
||||
logger.err( e, "While generating site description." );
|
||||
parameters.add( e.getLocalizedMessage() );
|
||||
}
|
||||
MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null;
|
||||
if (fileSite != null)
|
||||
parameters.add( Res.format( fileSite.getLastUsed() ) );
|
||||
parameters.add( site.getAlgorithm().version() );
|
||||
parameters.add( strf( "#%d", site.getCounter().longValue() ) );
|
||||
if ((fileSite != null) && (fileSite.getUrl() != null))
|
||||
parameters.add( fileSite.getUrl() );
|
||||
|
||||
return strf( "<html><strong>%s</strong> (%s)</html>", site.getSiteName(),
|
||||
return strf( "<html><strong>%s</strong> (%s)</html>", item.getKeyAsHTML(),
|
||||
Joiner.on( " - " ).skipNulls().join( parameters.build() ) );
|
||||
}
|
||||
|
||||
private void useSite() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
private String getQuestionDescription(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
|
||||
MPQuestion question = (item != null)? item.getOption(): null;
|
||||
if (question == null)
|
||||
return "<site>";
|
||||
if (question instanceof MPNewQuestion)
|
||||
return strf( "<html>%s <Add new question></html>", item.getKeyAsHTML() );
|
||||
|
||||
return strf( "<html>%s</html>", item.getKeyAsHTML() );
|
||||
}
|
||||
|
||||
private void useSite(final ActionEvent event) {
|
||||
MPSite<?> site = getSite();
|
||||
if (site instanceof MPNewSite) {
|
||||
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
|
||||
if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
|
||||
this, strf( "<html>Remember the site <strong>%s</strong>?</html>", site.getSiteName() ),
|
||||
"New Site", JOptionPane.YES_NO_OPTION )) {
|
||||
sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) );
|
||||
useSite();
|
||||
}
|
||||
return;
|
||||
"New Site", JOptionPane.YES_NO_OPTION ))
|
||||
return;
|
||||
|
||||
site = user.addSite( site.getSiteName() );
|
||||
}
|
||||
|
||||
showSiteResult( site, result -> {
|
||||
boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0;
|
||||
MPSite<?> fsite = site;
|
||||
Res.ui( getSiteResult( site, loginResult ), result -> {
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
if (site instanceof MPFileSite)
|
||||
((MPFileSite) site).use();
|
||||
if (fsite instanceof MPFileSite)
|
||||
((MPFileSite) fsite).use();
|
||||
|
||||
Transferable clipboardContents = new StringSelection( result );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
Res.ui( () -> {
|
||||
Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
|
||||
if (window instanceof Frame)
|
||||
((Frame) window).setExtendedState( Frame.ICONIFIED );
|
||||
} );
|
||||
copyResult( result );
|
||||
} );
|
||||
}
|
||||
|
||||
private void showSiteResult(@Nullable final MPSite<?> site) {
|
||||
showSiteResult( site, null );
|
||||
private void setShowLogin(final boolean showLogin) {
|
||||
if (showLogin == this.showLogin)
|
||||
return;
|
||||
|
||||
this.showLogin = showLogin;
|
||||
showSiteItem( sitesModel.getSelectedItem() );
|
||||
}
|
||||
|
||||
private void showSiteResult(@Nullable final MPSite<?> site, @Nullable final Consumer<String> resultCallback) {
|
||||
if (site == null) {
|
||||
if (resultCallback != null)
|
||||
resultCallback.accept( null );
|
||||
Res.ui( () -> {
|
||||
passwordLabel.setText( " " );
|
||||
passwordField.setText( " " );
|
||||
settingsButton.setEnabled( false );
|
||||
deleteButton.setEnabled( false );
|
||||
} );
|
||||
return;
|
||||
}
|
||||
private void showSiteItem(@Nullable final MPQuery.Result<? extends MPSite<?>> item) {
|
||||
MPSite<?> site = (item != null)? item.getOption(): null;
|
||||
Res.ui( getSiteResult( site, showLogin ), result -> {
|
||||
if (!showLogin && (site != null))
|
||||
resultLabel.setText( (result != null)? strf( "Your password for %s:", site.getSiteName() ): " " );
|
||||
else if (showLogin && (site != null))
|
||||
resultLabel.setText( (result != null)? strf( "Your login for %s:", site.getSiteName() ): " " );
|
||||
|
||||
Res.job( () -> {
|
||||
resultField.setText( (result != null)? result: " " );
|
||||
settingsButton.setEnabled( result != null );
|
||||
questionsButton.setEnabled( result != null );
|
||||
editButton.setEnabled( result != null );
|
||||
keyButton.setEnabled( result != null );
|
||||
deleteButton.setEnabled( result != null );
|
||||
} );
|
||||
}
|
||||
|
||||
private ListenableFuture<String> getSiteResult(@Nullable final MPSite<?> site, final boolean loginResult) {
|
||||
return Res.job( () -> {
|
||||
try {
|
||||
String result = site.getResult();
|
||||
if (resultCallback != null)
|
||||
resultCallback.accept( result );
|
||||
|
||||
Res.ui( () -> {
|
||||
passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
|
||||
passwordField.setText( result );
|
||||
settingsButton.setEnabled( true );
|
||||
deleteButton.setEnabled( true );
|
||||
} );
|
||||
if (site != null)
|
||||
return loginResult? site.getLogin(): site.getResult();
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.err( e, "While resolving password for: %s", site );
|
||||
}
|
||||
|
||||
return null;
|
||||
} );
|
||||
}
|
||||
|
||||
private void useQuestion(final ActionEvent event) {
|
||||
MPQuestion question = getQuestion();
|
||||
if (question instanceof MPNewQuestion) {
|
||||
if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
strf( "<html>Remember the security question with keyword <strong>%s</strong>?</html>",
|
||||
Strings.isNullOrEmpty( question.getKeyword() )? "<empty>": question.getKeyword() ),
|
||||
"New Question", JOptionPane.YES_NO_OPTION ))
|
||||
return;
|
||||
|
||||
question = question.getSite().addQuestion( question.getKeyword() );
|
||||
}
|
||||
|
||||
MPQuestion fquestion = question;
|
||||
Res.ui( getQuestionResult( question ), result -> {
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
if (fquestion instanceof MPFileQuestion)
|
||||
((MPFileQuestion) fquestion).use();
|
||||
|
||||
copyResult( result );
|
||||
} );
|
||||
}
|
||||
|
||||
private void showQuestionItem(@Nullable final MPQuery.Result<? extends MPQuestion> item) {
|
||||
MPQuestion question = (item != null)? item.getOption(): null;
|
||||
Res.ui( getQuestionResult( question ), answer -> {
|
||||
if ((answer == null) || (question == null))
|
||||
answerLabel.setText( " " );
|
||||
else
|
||||
answerLabel.setText(
|
||||
Strings.isNullOrEmpty( question.getKeyword() )?
|
||||
strf( "<html>Answer for site <b>%s</b>:", question.getSite().getSiteName() ):
|
||||
strf( "<html>Answer for site <b>%s</b>, of question with keyword <b>%s</b>:",
|
||||
question.getSite().getSiteName(), question.getKeyword() ) );
|
||||
answerField.setText( (answer != null)? answer: " " );
|
||||
} );
|
||||
}
|
||||
|
||||
private ListenableFuture<String> getQuestionResult(@Nullable final MPQuestion question) {
|
||||
return Res.job( () -> {
|
||||
try {
|
||||
if (question != null)
|
||||
return question.getAnswer();
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.err( e, "While resolving answer for: %s", question );
|
||||
}
|
||||
|
||||
return null;
|
||||
} );
|
||||
}
|
||||
|
||||
private void copyResult(final String result) {
|
||||
Transferable clipboardContents = new StringSelection( result );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
Res.ui( () -> {
|
||||
Window answerDialog = SwingUtilities.windowForComponent( answerField );
|
||||
if (answerDialog instanceof Dialog)
|
||||
answerDialog.setVisible( false );
|
||||
|
||||
Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
|
||||
if (window instanceof Frame)
|
||||
((Frame) window).setExtendedState( Frame.ICONIFIED );
|
||||
|
||||
setShowLogin( false );
|
||||
} );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MPSite<?> getSite() {
|
||||
MPQuery.Result<? extends MPSite<?>> selectedSite = sitesModel.getSelectedItem();
|
||||
if (selectedSite == null)
|
||||
return null;
|
||||
|
||||
return selectedSite.getOption();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MPQuestion getQuestion() {
|
||||
MPQuery.Result<? extends MPQuestion> selectedQuestion = questionsModel.getSelectedItem();
|
||||
if (selectedQuestion == null)
|
||||
return null;
|
||||
|
||||
return selectedQuestion.getOption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(final KeyEvent event) {
|
||||
}
|
||||
@@ -708,27 +1006,27 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
sitesList.dispatchEvent( event );
|
||||
}
|
||||
|
||||
private synchronized void updateSites(@Nullable final String query) {
|
||||
private synchronized void updateSites(@Nullable final String queryText) {
|
||||
if (updateSitesJob != null)
|
||||
updateSitesJob.cancel( true );
|
||||
|
||||
updateSitesJob = Res.job( () -> {
|
||||
Collection<MPSite<?>> sites = new LinkedList<>();
|
||||
if (!Strings.isNullOrEmpty( query )) {
|
||||
sites.addAll( new LinkedList<>( user.findSites( query ) ) );
|
||||
MPQuery query = new MPQuery( queryText );
|
||||
Collection<MPQuery.Result<? extends MPSite<?>>> siteItems =
|
||||
new LinkedList<>( user.findSites( query ) );
|
||||
|
||||
if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
|
||||
sites.add( new MPNewSite( user, query ) );
|
||||
}
|
||||
if (!Strings.isNullOrEmpty( queryText ))
|
||||
if (siteItems.stream().noneMatch( MPQuery.Result::isExact ))
|
||||
siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) );
|
||||
|
||||
Res.ui( () -> sitesModel.set( sites ) );
|
||||
Res.ui( () -> sitesModel.set( siteItems ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserUpdated(final MPUser<?> user) {
|
||||
updateSites( queryField.getText() );
|
||||
showSiteResult( sitesModel.getSelectedItem() );
|
||||
showSiteItem( sitesModel.getSelectedItem() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -738,5 +1036,13 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
@Override
|
||||
public void onUserInvalidated(final MPUser<?> user) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(final KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_SHIFT)
|
||||
setShowLogin( e.isShiftDown() );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,157 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-09-11
|
||||
*/
|
||||
public class MPQuery {
|
||||
|
||||
@Nonnull
|
||||
private final String query;
|
||||
|
||||
public MPQuery(@Nullable final String query) {
|
||||
this.query = (query != null)? query: "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 );
|
||||
if (query.isEmpty())
|
||||
return Optional.of( result );
|
||||
if (key.length() == 0)
|
||||
return Optional.empty();
|
||||
|
||||
// Consume query and key characters until one of them runs out, recording any matches against the result's key.
|
||||
int q = 0, k = 0;
|
||||
while ((q < query.length()) && (k < key.length())) {
|
||||
if (query.charAt( q ) == key.charAt( k )) {
|
||||
result.keyMatchedAt( k );
|
||||
++q;
|
||||
}
|
||||
|
||||
++k;
|
||||
}
|
||||
|
||||
// If query is consumed, the result is a hit.
|
||||
return (q >= query.length())? Optional.of( result ): Optional.empty();
|
||||
}
|
||||
|
||||
public static class Result<T extends Comparable<? super T>> implements Comparable<Result<T>> {
|
||||
|
||||
private final T option;
|
||||
private final CharSequence key;
|
||||
private final boolean[] keyMatches;
|
||||
|
||||
Result(final T option, final CharSequence key) {
|
||||
this.option = option;
|
||||
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 extends Comparable<? super T>> Result<T> allOf(final T option, final CharSequence key) {
|
||||
Result<T> result = noneOf( option, key );
|
||||
Arrays.fill( result.keyMatches, true );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public T getOption() {
|
||||
return option;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public CharSequence getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getKeyAsHTML() {
|
||||
return getKeyAsHTML( "u" );
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "MagicCharacter", "HardcodedFileSeparator" })
|
||||
public String getKeyAsHTML(final String mark) {
|
||||
String closeMark = mark.contains( " " )? mark.substring( 0, mark.indexOf( ' ' ) ): mark;
|
||||
StringBuilder html = new StringBuilder();
|
||||
boolean marked = false;
|
||||
|
||||
for (int i = 0; i < key.length(); ++i) {
|
||||
if (keyMatches[i] && !marked) {
|
||||
html.append( '<' ).append( mark ).append( '>' );
|
||||
marked = true;
|
||||
} else if (!keyMatches[i] && marked) {
|
||||
html.append( '<' ).append( '/' ).append( closeMark ).append( '>' );
|
||||
marked = false;
|
||||
}
|
||||
|
||||
html.append( key.charAt( i ) );
|
||||
}
|
||||
|
||||
if (marked)
|
||||
html.append( '<' ).append( '/' ).append( closeMark ).append( '>' );
|
||||
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
public boolean[] getKeyMatches() {
|
||||
return keyMatches.clone();
|
||||
}
|
||||
|
||||
public boolean isExact() {
|
||||
for (final boolean keyMatch : keyMatches)
|
||||
if (!keyMatch)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void keyMatchedAt(final int k) {
|
||||
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 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getOption().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{Result: %s}", key );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,13 @@ public interface MPQuestion extends Comparable<MPQuestion> {
|
||||
|
||||
void setType(MPResultType type);
|
||||
|
||||
@Nonnull
|
||||
default String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getAnswer( null );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
String getAnswer(@Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
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;
|
||||
@@ -57,28 +58,38 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
|
||||
void setLoginType(@Nullable MPResultType loginType);
|
||||
|
||||
@Nullable
|
||||
default String getResult()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getResult( MPKeyPurpose.Authentication );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
default String getResult(final MPKeyPurpose keyPurpose)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
return getResult( keyPurpose, null );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
return getResult( keyPurpose, keyContext, null );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
@Nullable
|
||||
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
@Nullable UnsignedInteger counter, MPResultType type, @Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
@Nonnull
|
||||
String getState(MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
@Nullable UnsignedInteger counter, MPResultType type, String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
@Nonnull
|
||||
default String getLogin()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@@ -94,10 +105,17 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
@Nonnull
|
||||
MPUser<?> getUser();
|
||||
|
||||
boolean addQuestion(Q question);
|
||||
@Nonnull
|
||||
Q addQuestion(String keyword);
|
||||
|
||||
@Nonnull
|
||||
Q addQuestion(Q question);
|
||||
|
||||
boolean deleteQuestion(Q question);
|
||||
|
||||
@Nonnull
|
||||
Collection<Q> getQuestions();
|
||||
|
||||
@Nonnull
|
||||
ImmutableCollection<MPQuery.Result<Q>> findQuestions(MPQuery query);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
|
||||
// - Relations
|
||||
|
||||
@Nonnull
|
||||
S addSite(String siteName);
|
||||
|
||||
@Nonnull
|
||||
@@ -111,11 +112,11 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
Collection<S> getSites();
|
||||
|
||||
@Nonnull
|
||||
ImmutableCollection<S> findSites(@Nullable String query);
|
||||
ImmutableCollection<MPQuery.Result<S>> findSites(MPQuery query);
|
||||
|
||||
boolean addListener(Listener listener);
|
||||
void addListener(Listener listener);
|
||||
|
||||
boolean removeListener(Listener listener);
|
||||
void removeListener(Listener listener);
|
||||
|
||||
interface Listener {
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPQuestion;
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -33,14 +34,23 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
|
||||
private final String keyword;
|
||||
private MPResultType type;
|
||||
private final MPSite<?> site;
|
||||
private final String keyword;
|
||||
|
||||
protected MPBasicQuestion(final String keyword, final MPResultType type) {
|
||||
private MPResultType type;
|
||||
|
||||
protected MPBasicQuestion(final MPSite<?> site, final String keyword, final MPResultType type) {
|
||||
this.site = site;
|
||||
this.keyword = keyword;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPSite<?> getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getKeyword() {
|
||||
@@ -55,7 +65,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
|
||||
@Override
|
||||
public void setType(final MPResultType type) {
|
||||
if (Objects.equals(this.type, type))
|
||||
if (this.type == type)
|
||||
return;
|
||||
|
||||
this.type = type;
|
||||
@@ -70,15 +80,12 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public abstract MPBasicSite<?, ?> getSite();
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
getSite().setChanged();
|
||||
if (site instanceof Changeable)
|
||||
((Changeable) site).setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,8 @@ 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.*;
|
||||
@@ -35,9 +37,9 @@ import javax.annotation.Nullable;
|
||||
public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable
|
||||
implements MPSite<Q> {
|
||||
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
private final U user;
|
||||
private final String siteName;
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
|
||||
private MPAlgorithm algorithm;
|
||||
private UnsignedInteger counter;
|
||||
@@ -104,7 +106,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
|
||||
@Override
|
||||
public void setResultType(final MPResultType resultType) {
|
||||
if (Objects.equals( this.resultType, resultType ))
|
||||
if (this.resultType == resultType)
|
||||
return;
|
||||
|
||||
this.resultType = resultType;
|
||||
@@ -119,14 +121,14 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
|
||||
@Override
|
||||
public void setLoginType(@Nullable final MPResultType loginType) {
|
||||
if (Objects.equals( this.loginType, loginType ))
|
||||
if (this.loginType == loginType)
|
||||
return;
|
||||
|
||||
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
@Override
|
||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@@ -134,8 +136,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
|
||||
}
|
||||
|
||||
protected String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
|
||||
@Nullable
|
||||
@Override
|
||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getUser().getMasterKey().siteResult(
|
||||
@@ -143,8 +147,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
keyPurpose, keyContext, type, state );
|
||||
}
|
||||
|
||||
protected String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getUser().getMasterKey().siteState(
|
||||
@@ -160,13 +166,13 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public boolean addQuestion(final Q question) {
|
||||
if (!questions.add( question ))
|
||||
return false;
|
||||
public Q addQuestion(final Q question) {
|
||||
questions.add( question );
|
||||
|
||||
setChanged();
|
||||
return true;
|
||||
return question;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,6 +190,16 @@ 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();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public U getUser() {
|
||||
|
||||
@@ -200,24 +200,22 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ImmutableCollection<S> findSites(@Nullable final String query) {
|
||||
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
|
||||
if (query != null)
|
||||
for (final S site : getSites())
|
||||
if (site.getSiteName().startsWith( query ))
|
||||
results.add( site );
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addListener(final Listener listener) {
|
||||
return listeners.add( listener );
|
||||
public void addListener(final Listener listener) {
|
||||
listeners.add( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeListener(final Listener listener) {
|
||||
return listeners.remove( listener );
|
||||
public void removeListener(final Listener listener) {
|
||||
listeners.remove( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,16 +30,13 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPFileQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPFileSite site;
|
||||
|
||||
@Nullable
|
||||
private String answerState;
|
||||
|
||||
public MPFileQuestion(final MPFileSite site, final String keyword,
|
||||
@Nullable final MPResultType type, @Nullable final String answerState) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
this.answerState = answerState;
|
||||
}
|
||||
|
||||
@@ -48,6 +45,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
return answerState;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
return getAnswer( answerState );
|
||||
@@ -66,9 +65,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileSite getSite() {
|
||||
return site;
|
||||
public void use() {
|
||||
if (getSite() instanceof MPFileSite)
|
||||
((MPFileSite) getSite()).use();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
@Override
|
||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@@ -145,6 +145,12 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileQuestion addQuestion(final String keyword) {
|
||||
return addQuestion( new MPFileQuestion( this, keyword, null, null ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@Nonnull final MPSite<?> o) {
|
||||
int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;
|
||||
|
||||
@@ -207,6 +207,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileSite addSite(final String siteName) {
|
||||
return addSite( new MPFileSite( this, siteName ) );
|
||||
|
||||
@@ -66,6 +66,7 @@ public class MPFileUserManager {
|
||||
|
||||
protected MPFileUserManager(final File path) {
|
||||
this.path = path;
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
@@ -128,12 +129,13 @@ public class MPFileUserManager {
|
||||
return ImmutableSortedSet.copyOf( userByName.values() );
|
||||
}
|
||||
|
||||
public boolean addListener(final Listener listener) {
|
||||
return listeners.add( listener );
|
||||
public void addListener(final Listener listener) {
|
||||
if (listeners.add( listener ))
|
||||
listener.onFilesUpdated( getFiles() );
|
||||
}
|
||||
|
||||
public boolean removeListener(final Listener listener) {
|
||||
return listeners.remove( listener );
|
||||
public void removeListener(final Listener listener) {
|
||||
listeners.remove( listener );
|
||||
}
|
||||
|
||||
private void fireUpdated() {
|
||||
@@ -141,7 +143,6 @@ public class MPFileUserManager {
|
||||
return;
|
||||
|
||||
ImmutableSortedSet<MPFileUser> files = getFiles();
|
||||
|
||||
for (final Listener listener : listeners)
|
||||
listener.onFilesUpdated( files );
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
if (fileSite.questions != null)
|
||||
for (final Map.Entry<String, Site.Question> questionEntry : fileSite.questions.entrySet()) {
|
||||
Site.Question fileQuestion = questionEntry.getValue();
|
||||
MPFileQuestion question = new MPFileQuestion( site, questionEntry.getKey(),
|
||||
MPFileQuestion question = new MPFileQuestion( site, ifNotNullElse( questionEntry.getKey(), "" ),
|
||||
fileQuestion.type, export.redacted? fileQuestion.answer: null );
|
||||
|
||||
if (!export.redacted && (fileQuestion.answer != null))
|
||||
|
||||
Submodule public/site updated: 2457f6dc9c...914a60cd25
Reference in New Issue
Block a user