From 7f178b5f67d94adfb5884d1dc65d4a7d27cbecd1 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 15 Jun 2021 16:50:05 +0200 Subject: [PATCH] jpms related changes for v1.4.2-beta --- build.gradle | 9 ++- src/main/deploy/package/osx/Info.plist | 2 +- .../sparrow/AboutController.java | 2 +- .../com/sparrowwallet/sparrow/MainApp.java | 3 +- .../com/sparrowwallet/sparrow/io/Hwi.java | 13 +-- .../com/sparrowwallet/sparrow/io/IOUtils.java | 80 +++++++++++++++++++ .../sparrow/io/db/DbPersistence.java | 48 +++++++++-- src/main/resources/logback.xml | 1 + 8 files changed, 132 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index dd43bf1e..2982b9bf 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { 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 osName = os.getFamilyName() if(os.macOsX) { @@ -50,7 +50,7 @@ dependencies { exclude group: 'org.slf4j' } 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('no.tornado:tornadofx-controls:1.0.4') implementation('com.google.zxing:javase:3.4.0') @@ -138,6 +138,8 @@ jlink { requires 'com.fasterxml.jackson.databind' requires 'jdk.crypto.cryptoki' 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'] @@ -161,7 +163,8 @@ jlink { "--add-opens=javafx.graphics/com.sun.javafx.application=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.sql"] + "--add-reads=com.sparrowwallet.merged.module=java.sql", + "--add-reads=com.sparrowwallet.merged.module=com.sparrowwallet.sparrow"] if(os.macOsX) { jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module" diff --git a/src/main/deploy/package/osx/Info.plist b/src/main/deploy/package/osx/Info.plist index 9771859b..5182aabd 100644 --- a/src/main/deploy/package/osx/Info.plist +++ b/src/main/deploy/package/osx/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.1 + 1.4.2 CFBundleSignature ???? diff --git a/src/main/java/com/sparrowwallet/sparrow/AboutController.java b/src/main/java/com/sparrowwallet/sparrow/AboutController.java index 4ae2d8d3..b3dcbc23 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AboutController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AboutController.java @@ -12,7 +12,7 @@ public class AboutController { private Label title; 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) { diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index a2f5fed9..6717c22f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -31,7 +31,8 @@ import java.util.stream.Collectors; public class MainApp extends Application { public static final String APP_ID = "com.sparrowwallet.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 NETWORK_ENV_PROPERTY = "SPARROW_NETWORK"; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java index 52a538e8..c9e1bb8b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java @@ -230,7 +230,7 @@ public class Hwi { String hwiPath = hwiExecutable.getAbsolutePath(); if(command.isTestFirst() && (hwiPath.contains(tmpDir) || hwiPath.startsWith(homeDir.getAbsolutePath())) && (!hwiPath.contains(HWI_VERSION_DIR) || !testHwi(hwiExecutable))) { if(Platform.getCurrent() == Platform.OSX) { - deleteDirectory(hwiExecutable.getParentFile()); + IOUtils.deleteDirectory(hwiExecutable.getParentFile()); } else { 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 setFilePermissions) throws IOException { String destDirPath = destinationDir.getCanonicalPath(); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java b/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java index 4524588c..c93ac253 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java @@ -1,8 +1,17 @@ package com.sparrowwallet.sparrow.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.file.FileSystems; 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 static FileType getFileType(File file) { @@ -38,4 +47,75 @@ public class IOUtils { 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 entries = jar.entries(); //gives ALL entries in jar + Set result = new HashSet(); //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; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java index a030f870..0fc69c95 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.io.db; import com.google.common.eventbus.Subscribe; +import com.google.common.io.Files; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.crypto.Argon2KeyDeriver; import com.sparrowwallet.drongo.crypto.AsymmetricKeyDeriver; @@ -29,6 +30,7 @@ import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.StandardCopyOption; import java.security.SecureRandom; import java.util.*; 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); private static final String H2_USER = "sa"; private static final String H2_PASSWORD = ""; + public static final String MIGRATION_RESOURCES_DIR = "com/sparrowwallet/sparrow/sql/"; private HikariDataSource dataSource; private AsymmetricKeyDeriver keyDeriver; @@ -299,8 +302,9 @@ public class DbPersistence implements Persistence { } private void migrate(Storage storage, String schema, ECKey encryptionKey) throws StorageException { + File migrationDir = getMigrationDir(); try { - Flyway flyway = getFlyway(storage, schema, getFilePassword(encryptionKey)); + Flyway flyway = getFlyway(storage, schema, getFilePassword(encryptionKey), migrationDir); flyway.migrate(); } catch(FlywayValidateException 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) { log.error("Failed to open wallet file. ", 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 { + File migrationDir = getMigrationDir(); try { - boolean existing = (dataSource == null); - Flyway flyway = getFlyway(storage, schema, password); - if(existing) { - flyway.clean(); - } + Flyway flyway = getFlyway(storage, schema, password, migrationDir); + flyway.clean(); flyway.migrate(); } catch(FlywayException e) { log.error("Failed to save wallet file.", 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; } - private Flyway getFlyway(Storage storage, String schema, String password) throws StorageException { - return Flyway.configure().dataSource(getDataSource(storage, password)).locations("com/sparrowwallet/sparrow/sql").schemas(schema).load(); + private Flyway getFlyway(Storage storage, String schema, String password, File resourcesDir) throws StorageException { + 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 { @@ -518,11 +546,15 @@ public class DbPersistence implements Persistence { private HikariDataSource createDataSource(File walletFile, String password) throws StorageException { try { + Class.forName("org.h2.Driver"); HikariConfig config = new HikariConfig(); config.setJdbcUrl(getUrl(walletFile, password)); config.setUsername(H2_USER); config.setPassword(password == null ? H2_PASSWORD : password + " " + H2_PASSWORD); 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) { 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); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 7291e7f0..7216bbc9 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,6 +9,7 @@ +