Browse Source

Native: Clean up left over temp files

On Windows the native libraries weren't deleted after jvm shutdown
as expected. This was reported by itsTyrion in #325. This implementation is
based on #326 but makes it more robust against multiple simultaneous uses of
the NativeUtil library (e.g. the auto-dark-mode plugin for IntelliJ also makes
use of it).
pull/329/head
itsTyrion 2 years ago committed by Jannis Weis
parent
commit
ce97823d45
No known key found for this signature in database
GPG Key ID: 7C9D8D4B558049AB
  1. 9
      native-utils/src/main/java/com/github/weisj/darklaf/nativeutil/AbstractLibrary.java
  2. 72
      native-utils/src/main/java/com/github/weisj/darklaf/nativeutil/NativeUtil.java

9
native-utils/src/main/java/com/github/weisj/darklaf/nativeutil/AbstractLibrary.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
@ -27,6 +27,7 @@ import java.util.logging.Logger;
public abstract class AbstractLibrary {
private static final String ILLEGAL_PATH_CHARACTERS = "[\\\\/:*?\"<>|]";
private final String name;
protected final Logger logger;
private boolean loaded;
@ -60,10 +61,12 @@ public abstract class AbstractLibrary {
String path = getLibraryPath();
if (path != null && !path.isEmpty()) {
List<NativeUtil.Resource> resources = getResourcePaths();
String libraryIdentifier = name.replaceAll(ILLEGAL_PATH_CHARACTERS, "");
if (resources == null || resources.isEmpty()) {
NativeUtil.loadLibraryFromJar(getLoaderClass(), path);
NativeUtil.loadLibraryFromJar(getLoaderClass(), path, libraryIdentifier);
} else {
NativeUtil.loadLibraryFromJarWithExtraResources(getLoaderClass(), path, resources);
NativeUtil.loadLibraryFromJarWithExtraResources(getLoaderClass(), path, resources,
libraryIdentifier);
}
loaded = true;
info("Loaded " + name + " at " + path + ".");

72
native-utils/src/main/java/com/github/weisj/darklaf/nativeutil/NativeUtil.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
@ -26,10 +26,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
/**
@ -45,7 +47,7 @@ import java.util.logging.Logger;
public final class NativeUtil {
private static final Logger LOGGER = Logger.getLogger(NativeUtil.class.getName());
public static final String NATIVE_FOLDER_PATH_PREFIX = "nativeutils";
public static final String NATIVE_FOLDER_PATH_PREFIX = "com-weisj-darklaf-nativeutils";
/**
* The minimum length a prefix for a file has to have according to
* {@link File#createTempFile(String, String)}}.
@ -77,6 +79,7 @@ public final class NativeUtil {
* @param loaderClass the class to use for loading.
* @param path The path of file inside JAR as absolute path (beginning with '/'), e.g.
* /package/File.ext
* @param identifier The library identifier for clean-up purposes
* @throws IOException If temporary file creation or read/write operation fails
* @throws IllegalArgumentException If source file (param path) does not exist
* @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than
@ -85,20 +88,25 @@ public final class NativeUtil {
* @throws FileNotFoundException If the file could not be found inside the JAR.
*/
public static void loadLibraryFromJarWithExtraResources(final Class<?> loaderClass, final String path,
final List<Resource> resources)
final List<Resource> resources, final String identifier)
throws IOException {
List<Path> resourcePaths = extractResources(loaderClass, resources);
String libraryIdentifier = getFullLibraryIdentifier(identifier);
Path tempDir = getTemporaryDirectory(libraryIdentifier);
List<Path> resourcePaths = extractResources(loaderClass, resources, tempDir);
try {
loadLibraryFromJar(loaderClass, path);
doLoadLibraryFromJar(loaderClass, path, identifier, tempDir);
} finally {
resourcePaths.forEach(NativeUtil::releaseResource);
}
}
private static List<Path> extractResources(final Class<?> caller, final List<Resource> resources)
private static List<Path> extractResources(final Class<?> caller, final List<Resource> resources,
final Path tempDir)
throws IOException {
List<Path> paths = new ArrayList<>(resources.size());
Path tempDir = getTemporaryDirectory();
for (Resource resource : resources) {
String filename = getFileNameFromPath(resource.filePath);
Path destinationDir = tempDir.resolve(resource.destinationDirectoryPath);
@ -126,6 +134,7 @@ public final class NativeUtil {
* @param loaderClass the class to use for loading.
* @param path The path of file inside JAR as absolute path (beginning with '/'), e.g.
* /package/File.ext
* @param identifier The library identifier for clean-up purposes
* @throws IOException If temporary file creation or read/write operation fails
* @throws IllegalArgumentException If source file (param path) does not exist
* @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than
@ -133,7 +142,15 @@ public final class NativeUtil {
* {@link File#createTempFile(java.lang.String, java.lang.String)}).
* @throws FileNotFoundException If the file could not be found inside the JAR.
*/
public static void loadLibraryFromJar(final Class<?> loaderClass, final String path) throws IOException {
public static void loadLibraryFromJar(final Class<?> loaderClass, final String path, final String identifier)
throws IOException {
String libraryIdentifier = getFullLibraryIdentifier(identifier);
Path tempDir = getTemporaryDirectory(libraryIdentifier);
doLoadLibraryFromJar(loaderClass, path, identifier, tempDir);
}
private static void doLoadLibraryFromJar(final Class<?> loaderClass, final String path, final String identifier,
final Path tempDir) throws IOException {
String filename = getFileNameFromPath(path);
// Check if the filename is okay
@ -142,9 +159,12 @@ public final class NativeUtil {
}
// Prepare temporary file
Path tempDir = getTemporaryDirectory();
String libraryIdentifier = getFullLibraryIdentifier(identifier);
Path temp = tempDir.resolve(filename);
if (!isPosixCompliant()) {
deleteLeftoverTempFiles(tempDir, libraryIdentifier);
}
extractFile(loaderClass, path, tempDir, temp);
try {
@ -161,7 +181,7 @@ public final class NativeUtil {
if (is == null) throw new FileNotFoundException("File " + path + " was not found inside JAR.");
if (!destinationDir.toFile().canWrite()) throw new IOException("Can't write to temporary directory.");
if (!Files.exists(destinationPath)) {
// Otherwise the file is already existent and most probably loaded.
// Otherwise, the file is already existent and most probably loaded.
Files.copy(is, destinationPath.toAbsolutePath(), StandardCopyOption.REPLACE_EXISTING);
}
} catch (final IOException e) {
@ -170,6 +190,30 @@ public final class NativeUtil {
}
}
private static void deleteLeftoverTempFiles(Path tempDir, String identifier) throws IOException {
try (Stream<Path> files = Files.list(tempDir.getParent())) {
files.filter(Files::isDirectory)
.filter(p -> !tempDir.equals(p))
.filter(p -> p.getFileName().toString().startsWith(identifier))
.forEach(NativeUtil::deleteFolder);
}
}
/**
* Recursively deletes a folder and it's files
*
* @param folder the target folder as {@link File}
*/
private static void deleteFolder(Path folder) {
LOGGER.fine("Removing " + folder);
try (Stream<Path> walk = Files.walk(folder)) {
walk.sorted(Comparator.reverseOrder())
.forEach(NativeUtil::delete);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not delete directory", e);
}
}
private static String getFileNameFromPath(final String path) {
checkPath(path);
String[] parts = path.split("/");
@ -182,9 +226,13 @@ public final class NativeUtil {
}
}
private static Path getTemporaryDirectory() throws IOException {
private static String getFullLibraryIdentifier(final String identifier) {
return NATIVE_FOLDER_PATH_PREFIX + "-" + identifier;
}
private static Path getTemporaryDirectory(final String libraryIdentifier) throws IOException {
if (temporaryDir == null) {
temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX);
temporaryDir = createTempDirectory(libraryIdentifier);
temporaryDir.toFile().deleteOnExit();
}
return temporaryDir;

Loading…
Cancel
Save