Skip to content

Commit

Permalink
Native: Clean up left over temp files
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
itsTyrion authored and weisJ committed Jul 31, 2022
1 parent 664ac79 commit ce97823
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 15 deletions.
@@ -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,
Expand All @@ -27,6 +27,7 @@

public abstract class AbstractLibrary {

private static final String ILLEGAL_PATH_CHARACTERS = "[\\\\/:*?\"<>|]";
private final String name;
protected final Logger logger;
private boolean loaded;
Expand Down Expand Up @@ -60,10 +61,12 @@ private void loadLibrary() {
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 + ".");
Expand Down
@@ -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,
Expand All @@ -26,10 +26,12 @@
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;


/**
Expand All @@ -45,7 +47,7 @@
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)}}.
Expand Down Expand Up @@ -77,6 +79,7 @@ public Resource(final String filePath, final String destinationDirectoryPath) {
* @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
Expand All @@ -85,20 +88,25 @@ public Resource(final String filePath, final String destinationDirectoryPath) {
* @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);
Expand Down Expand Up @@ -126,14 +134,23 @@ private static List<Path> extractResources(final Class<?> caller, final List<Res
* @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
* three characters (restriction of
* {@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
Expand All @@ -142,9 +159,12 @@ public static void loadLibraryFromJar(final Class<?> loaderClass, final String p
}

// 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 {
Expand All @@ -161,7 +181,7 @@ private static void extractFile(final Class<?> loaderClass, final String path, f
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) {
Expand All @@ -170,6 +190,30 @@ private static void extractFile(final Class<?> loaderClass, final String path, f
}
}

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("/");
Expand All @@ -182,9 +226,13 @@ private static void checkPath(final String path) {
}
}

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;
Expand Down

0 comments on commit ce97823

Please sign in to comment.