jpms related changes for v1.4.2-beta

This commit is contained in:
Craig Raw 2021-06-15 16:50:05 +02:00
parent 1208baf00e
commit 7f178b5f67
8 changed files with 132 additions and 26 deletions

View file

@ -5,7 +5,7 @@ plugins {
id 'org.beryx.jlink' version '2.22.0' id 'org.beryx.jlink' version '2.22.0'
} }
def sparrowVersion = '1.4.1' def sparrowVersion = '1.4.2'
def os = org.gradle.internal.os.OperatingSystem.current() def os = org.gradle.internal.os.OperatingSystem.current()
def osName = os.getFamilyName() def osName = os.getFamilyName()
if(os.macOsX) { if(os.macOsX) {
@ -50,7 +50,7 @@ dependencies {
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
implementation('org.jdbi:jdbi3-sqlobject:3.20.0') implementation('org.jdbi:jdbi3-sqlobject:3.20.0')
implementation('org.flywaydb:flyway-core:7.10.1-SNAPSHOT') implementation('org.flywaydb:flyway-core:7.10.5-SNAPSHOT')
implementation('org.fxmisc.richtext:richtextfx:0.10.4') implementation('org.fxmisc.richtext:richtextfx:0.10.4')
implementation('no.tornado:tornadofx-controls:1.0.4') implementation('no.tornado:tornadofx-controls:1.0.4')
implementation('com.google.zxing:javase:3.4.0') implementation('com.google.zxing:javase:3.4.0')
@ -138,6 +138,8 @@ jlink {
requires 'com.fasterxml.jackson.databind' requires 'com.fasterxml.jackson.databind'
requires 'jdk.crypto.cryptoki' requires 'jdk.crypto.cryptoki'
requires 'java.management' requires 'java.management'
uses 'org.flywaydb.core.extensibility.FlywayExtension'
uses 'org.flywaydb.core.internal.database.DatabaseType'
} }
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png'] options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png']
@ -161,7 +163,8 @@ jlink {
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
"--add-opens=java.base/java.net=com.sparrowwallet.sparrow", "--add-opens=java.base/java.net=com.sparrowwallet.sparrow",
"--add-reads=com.sparrowwallet.merged.module=java.desktop", "--add-reads=com.sparrowwallet.merged.module=java.desktop",
"--add-reads=com.sparrowwallet.merged.module=java.sql"] "--add-reads=com.sparrowwallet.merged.module=java.sql",
"--add-reads=com.sparrowwallet.merged.module=com.sparrowwallet.sparrow"]
if(os.macOsX) { if(os.macOsX) {
jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module" jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module"

View file

@ -21,7 +21,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.4.1</string> <string>1.4.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories --> <!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->

View file

@ -12,7 +12,7 @@ public class AboutController {
private Label title; private Label title;
public void initializeView() { public void initializeView() {
title.setText(MainApp.APP_NAME + " " + MainApp.APP_VERSION); title.setText(MainApp.APP_NAME + " " + MainApp.APP_VERSION + MainApp.APP_VERSION_SUFFIX);
} }
public void setStage(Stage stage) { public void setStage(Stage stage) {

View file

@ -31,7 +31,8 @@ import java.util.stream.Collectors;
public class MainApp extends Application { public class MainApp extends Application {
public static final String APP_ID = "com.sparrowwallet.sparrow"; public static final String APP_ID = "com.sparrowwallet.sparrow";
public static final String APP_NAME = "Sparrow"; public static final String APP_NAME = "Sparrow";
public static final String APP_VERSION = "1.4.1"; public static final String APP_VERSION = "1.4.2";
public static final String APP_VERSION_SUFFIX = "-beta";
public static final String APP_HOME_PROPERTY = "sparrow.home"; public static final String APP_HOME_PROPERTY = "sparrow.home";
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK"; public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";

View file

@ -230,7 +230,7 @@ public class Hwi {
String hwiPath = hwiExecutable.getAbsolutePath(); String hwiPath = hwiExecutable.getAbsolutePath();
if(command.isTestFirst() && (hwiPath.contains(tmpDir) || hwiPath.startsWith(homeDir.getAbsolutePath())) && (!hwiPath.contains(HWI_VERSION_DIR) || !testHwi(hwiExecutable))) { if(command.isTestFirst() && (hwiPath.contains(tmpDir) || hwiPath.startsWith(homeDir.getAbsolutePath())) && (!hwiPath.contains(HWI_VERSION_DIR) || !testHwi(hwiExecutable))) {
if(Platform.getCurrent() == Platform.OSX) { if(Platform.getCurrent() == Platform.OSX) {
deleteDirectory(hwiExecutable.getParentFile()); IOUtils.deleteDirectory(hwiExecutable.getParentFile());
} else { } else {
hwiExecutable.delete(); hwiExecutable.delete();
} }
@ -339,17 +339,6 @@ public class Hwi {
} }
} }
private boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
public static File newDirectory(File destinationDir, ZipEntry zipEntry, Set<PosixFilePermission> setFilePermissions) throws IOException { public static File newDirectory(File destinationDir, ZipEntry zipEntry, Set<PosixFilePermission> setFilePermissions) throws IOException {
String destDirPath = destinationDir.getCanonicalPath(); String destDirPath = destinationDir.getCanonicalPath();

View file

@ -1,8 +1,17 @@
package com.sparrowwallet.sparrow.io; package com.sparrowwallet.sparrow.io;
import java.io.*; import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class IOUtils { public class IOUtils {
public static FileType getFileType(File file) { public static FileType getFileType(File file) {
@ -38,4 +47,75 @@ public class IOUtils {
return FileType.UNKNOWN; return FileType.UNKNOWN;
} }
/**
* List directory contents for a resource folder. Not recursive.
* This is basically a brute-force implementation.
* Works for regular files, JARs and Java modules.
*
* @param clazz Any java class that lives in the same place as the resources you want.
* @param path Should end with "/", but not start with one.
* @return Just the name of each member item, not the full paths.
* @throws URISyntaxException
* @throws IOException
*/
public static String[] getResourceListing(Class clazz, String path) throws URISyntaxException, IOException {
URL dirURL = clazz.getClassLoader().getResource(path);
if(dirURL != null && dirURL.getProtocol().equals("file")) {
/* A file path: easy enough */
return new File(dirURL.toURI()).list();
}
if(dirURL == null) {
/*
* In case of a jar file, we can't actually find a directory.
* Have to assume the same jar as clazz.
*/
String me = clazz.getName().replace(".", "/")+".class";
dirURL = clazz.getClassLoader().getResource(me);
}
if(dirURL.getProtocol().equals("jar")) {
/* A JAR path */
String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
while(entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if(name.startsWith(path)) { //filter according to the path
String entry = name.substring(path.length());
int checkSubdir = entry.indexOf("/");
if (checkSubdir >= 0) {
// if it is a subdirectory, we just return the directory name
entry = entry.substring(0, checkSubdir);
}
result.add(entry);
}
}
return result.toArray(new String[result.size()]);
}
if(dirURL.getProtocol().equals("jrt")) {
java.nio.file.FileSystem jrtFs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap());
Path resourcePath = jrtFs.getPath("modules/com.sparrowwallet.sparrow", path);
return Files.list(resourcePath).map(filePath -> filePath.getFileName().toString()).toArray(String[]::new);
}
throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
}
public static boolean deleteDirectory(File directory) {
try {
Files.walk(directory.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} catch(IOException e) {
return false;
}
return true;
}
} }

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.io.db; package com.sparrowwallet.sparrow.io.db;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.common.io.Files;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.Argon2KeyDeriver; import com.sparrowwallet.drongo.crypto.Argon2KeyDeriver;
import com.sparrowwallet.drongo.crypto.AsymmetricKeyDeriver; import com.sparrowwallet.drongo.crypto.AsymmetricKeyDeriver;
@ -29,6 +30,7 @@ import java.io.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.StandardCopyOption;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -46,6 +48,7 @@ public class DbPersistence implements Persistence {
public static final byte[] HEADER_MAGIC_1 = "SPRW1\n".getBytes(StandardCharsets.UTF_8); public static final byte[] HEADER_MAGIC_1 = "SPRW1\n".getBytes(StandardCharsets.UTF_8);
private static final String H2_USER = "sa"; private static final String H2_USER = "sa";
private static final String H2_PASSWORD = ""; private static final String H2_PASSWORD = "";
public static final String MIGRATION_RESOURCES_DIR = "com/sparrowwallet/sparrow/sql/";
private HikariDataSource dataSource; private HikariDataSource dataSource;
private AsymmetricKeyDeriver keyDeriver; private AsymmetricKeyDeriver keyDeriver;
@ -299,8 +302,9 @@ public class DbPersistence implements Persistence {
} }
private void migrate(Storage storage, String schema, ECKey encryptionKey) throws StorageException { private void migrate(Storage storage, String schema, ECKey encryptionKey) throws StorageException {
File migrationDir = getMigrationDir();
try { try {
Flyway flyway = getFlyway(storage, schema, getFilePassword(encryptionKey)); Flyway flyway = getFlyway(storage, schema, getFilePassword(encryptionKey), migrationDir);
flyway.migrate(); flyway.migrate();
} catch(FlywayValidateException e) { } catch(FlywayValidateException e) {
log.error("Failed to open wallet file. Validation error during schema migration.", e); log.error("Failed to open wallet file. Validation error during schema migration.", e);
@ -308,20 +312,22 @@ public class DbPersistence implements Persistence {
} catch(FlywayException e) { } catch(FlywayException e) {
log.error("Failed to open wallet file. ", e); log.error("Failed to open wallet file. ", e);
throw new StorageException("Failed to open wallet file.\n" + e.getMessage(), e); throw new StorageException("Failed to open wallet file.\n" + e.getMessage(), e);
} finally {
IOUtils.deleteDirectory(migrationDir);
} }
} }
private void cleanAndMigrate(Storage storage, String schema, String password) throws StorageException { private void cleanAndMigrate(Storage storage, String schema, String password) throws StorageException {
File migrationDir = getMigrationDir();
try { try {
boolean existing = (dataSource == null); Flyway flyway = getFlyway(storage, schema, password, migrationDir);
Flyway flyway = getFlyway(storage, schema, password); flyway.clean();
if(existing) {
flyway.clean();
}
flyway.migrate(); flyway.migrate();
} catch(FlywayException e) { } catch(FlywayException e) {
log.error("Failed to save wallet file.", e); log.error("Failed to save wallet file.", e);
throw new StorageException("Failed to save wallet file.\n" + e.getMessage(), e); throw new StorageException("Failed to save wallet file.\n" + e.getMessage(), e);
} finally {
IOUtils.deleteDirectory(migrationDir);
} }
} }
@ -504,8 +510,30 @@ public class DbPersistence implements Persistence {
return jdbi; return jdbi;
} }
private Flyway getFlyway(Storage storage, String schema, String password) throws StorageException { private Flyway getFlyway(Storage storage, String schema, String password, File resourcesDir) throws StorageException {
return Flyway.configure().dataSource(getDataSource(storage, password)).locations("com/sparrowwallet/sparrow/sql").schemas(schema).load(); return Flyway.configure().dataSource(getDataSource(storage, password)).locations("filesystem:" + resourcesDir.getAbsolutePath()).schemas(schema).failOnMissingLocations(true).load();
}
//Flyway does not support JPMS yet, so the migration files are extracted to a temp dir in order to avoid classloader encapsulation issues
private File getMigrationDir() {
File migrationDir = Files.createTempDir();
try {
String[] files = IOUtils.getResourceListing(DbPersistence.class, MIGRATION_RESOURCES_DIR);
for(String name : files) {
File targetFile = new File(migrationDir, name);
try(InputStream inputStream = DbPersistence.class.getResourceAsStream("/" + MIGRATION_RESOURCES_DIR + name)) {
if(inputStream != null) {
java.nio.file.Files.copy(inputStream, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} else {
log.error("Could not load resource at /" + MIGRATION_RESOURCES_DIR + name);
}
}
}
} catch(Exception e) {
log.error("Could not extract migration resources", e);
}
return migrationDir;
} }
private HikariDataSource getDataSource(Storage storage, String password) throws StorageException { private HikariDataSource getDataSource(Storage storage, String password) throws StorageException {
@ -518,11 +546,15 @@ public class DbPersistence implements Persistence {
private HikariDataSource createDataSource(File walletFile, String password) throws StorageException { private HikariDataSource createDataSource(File walletFile, String password) throws StorageException {
try { try {
Class.forName("org.h2.Driver");
HikariConfig config = new HikariConfig(); HikariConfig config = new HikariConfig();
config.setJdbcUrl(getUrl(walletFile, password)); config.setJdbcUrl(getUrl(walletFile, password));
config.setUsername(H2_USER); config.setUsername(H2_USER);
config.setPassword(password == null ? H2_PASSWORD : password + " " + H2_PASSWORD); config.setPassword(password == null ? H2_PASSWORD : password + " " + H2_PASSWORD);
return new HikariDataSource(config); return new HikariDataSource(config);
} catch(ClassNotFoundException e) {
log.error("Cannot find H2 driver", e);
throw new StorageException("Cannot find H2 driver", e);
} catch(HikariPool.PoolInitializationException e) { } catch(HikariPool.PoolInitializationException e) {
if(e.getMessage() != null && e.getMessage().contains("Database may be already in use")) { if(e.getMessage() != null && e.getMessage().contains("Database may be already in use")) {
log.error("Wallet file may already be in use. Make sure the application is not running elsewhere.", e); log.error("Wallet file may already be in use. Make sure the application is not running elsewhere.", e);

View file

@ -9,6 +9,7 @@
<logger name="sun.net.www.protocol.http.HttpURLConnection" level="INFO" /> <logger name="sun.net.www.protocol.http.HttpURLConnection" level="INFO" />
<logger name="h2database" level="ERROR" /> <logger name="h2database" level="ERROR" />
<logger name="com.zaxxer.hikari.HikariDataSource" level="WARN" /> <logger name="com.zaxxer.hikari.HikariDataSource" level="WARN" />
<logger name="com.zaxxer.hikari.pool.HikariPool" level="ERROR" />
<logger name="org.flywaydb.core.internal.command.DbValidate" level="WARN" /> <logger name="org.flywaydb.core.internal.command.DbValidate" level="WARN" />
<logger name="org.flywaydb.core.internal.command.DbMigrate" level="WARN" /> <logger name="org.flywaydb.core.internal.command.DbMigrate" level="WARN" />
<logger name="org.flywaydb.core.internal.command.DbClean" level="ERROR" /> <logger name="org.flywaydb.core.internal.command.DbClean" level="ERROR" />