mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-1164: Reorganizing Ghidra's user settings/cache/temp directories to support XDG
This commit is contained in:
parent
8bfcb02166
commit
3c30ada14c
50 changed files with 919 additions and 222 deletions
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
package ghidra;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
|
@ -36,12 +37,10 @@ public class GhidraApplicationLayout extends ApplicationLayout {
|
|||
/**
|
||||
* Constructs a new Ghidra application layout object.
|
||||
*
|
||||
* @throws FileNotFoundException if there was a problem getting a user
|
||||
* directory.
|
||||
* @throws IOException if there was a problem getting the application
|
||||
* properties or modules.
|
||||
* @throws IOException if there was a problem getting a user directory or the application
|
||||
* properties or modules.
|
||||
*/
|
||||
public GhidraApplicationLayout() throws FileNotFoundException, IOException {
|
||||
public GhidraApplicationLayout() throws IOException {
|
||||
|
||||
// Application root directories
|
||||
applicationRootDirs = findGhidraApplicationRootDirs();
|
||||
|
@ -53,7 +52,8 @@ public class GhidraApplicationLayout extends ApplicationLayout {
|
|||
applicationInstallationDir = findGhidraApplicationInstallationDir();
|
||||
|
||||
// User directories
|
||||
userTempDir = ApplicationUtilities.getDefaultUserTempDir(getApplicationProperties());
|
||||
userTempDir = ApplicationUtilities
|
||||
.getDefaultUserTempDir(getApplicationProperties().getApplicationName());
|
||||
userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties());
|
||||
userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(getApplicationProperties(),
|
||||
getApplicationInstallationDir());
|
||||
|
@ -77,13 +77,10 @@ public class GhidraApplicationLayout extends ApplicationLayout {
|
|||
* (like the Eclipse GhidraDevPlugin).
|
||||
*
|
||||
* @param applicationInstallationDir The application installation directory.
|
||||
* @throws FileNotFoundException if there was a problem getting a user
|
||||
* directory.
|
||||
* @throws IOException if there was a problem getting the application
|
||||
* properties.
|
||||
* @throws IOException if there was a problem getting a user directory or the application
|
||||
* properties.
|
||||
*/
|
||||
public GhidraApplicationLayout(File applicationInstallationDir)
|
||||
throws FileNotFoundException, IOException {
|
||||
public GhidraApplicationLayout(File applicationInstallationDir) throws IOException {
|
||||
|
||||
// Application installation directory
|
||||
this.applicationInstallationDir = new ResourceFile(applicationInstallationDir);
|
||||
|
@ -96,7 +93,8 @@ public class GhidraApplicationLayout extends ApplicationLayout {
|
|||
applicationProperties = new ApplicationProperties(applicationRootDirs);
|
||||
|
||||
// User directories
|
||||
userTempDir = ApplicationUtilities.getDefaultUserTempDir(getApplicationProperties());
|
||||
userTempDir = ApplicationUtilities
|
||||
.getDefaultUserTempDir(getApplicationProperties().getApplicationName());
|
||||
userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties());
|
||||
userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(getApplicationProperties(),
|
||||
getApplicationInstallationDir());
|
||||
|
|
|
@ -140,7 +140,7 @@ public class SystemUtilities {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean value of the system property by the given name. If the property is
|
||||
* Gets the boolean value of the system property by the given name. If the property is
|
||||
* not set, the defaultValue is returned. If the value is set, then it will be passed
|
||||
* into {@link Boolean#parseBoolean(String)}.
|
||||
*
|
||||
|
|
|
@ -1189,29 +1189,6 @@ public final class FileUtilities {
|
|||
return formatter.format((length / 1000000f)) + "MB";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary directory using the given prefix
|
||||
* @param prefix the prefix
|
||||
* @return the temp file
|
||||
*/
|
||||
public static File createTempDirectory(String prefix) {
|
||||
try {
|
||||
File temp = File.createTempFile(prefix, Long.toString(System.currentTimeMillis()));
|
||||
if (!temp.delete()) {
|
||||
throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
|
||||
}
|
||||
if (!createDir(temp)) {
|
||||
throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(FileUtilities.class, "Error creating temporary directory", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given file (or directory) to readable and writable by only the owner.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package utility.application;
|
||||
|
||||
import static utility.application.ApplicationUtilities.*;
|
||||
import static utility.application.XdgUtils.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.ApplicationProperties;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Interactive utility to discover and delete artifacts that Ghidra lays down on the filesystem
|
||||
*/
|
||||
public class AppCleaner implements GhidraLaunchable {
|
||||
|
||||
/**
|
||||
* Launches the {@link AppCleaner}
|
||||
*
|
||||
* @param layout The application layout to use for the launch
|
||||
* @param args One argument is expected: the name of the application to clean. All other
|
||||
* arguments are ignored.
|
||||
* @throws Exception if there was a problem with the launch
|
||||
*/
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) throws Exception {
|
||||
|
||||
if (args.length != 1) {
|
||||
System.out.println("Expected 1 argument but got " + args.length);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String appName = args[0];
|
||||
System.out.println("\nDiscovering " + appName + " artifact directories....");
|
||||
|
||||
// Discover directories
|
||||
Set<File> discoveredSet = new LinkedHashSet<>();
|
||||
discoveredSet.addAll(findSettingsDirs(appName, layout));
|
||||
discoveredSet.addAll(findCacheDirs(appName, layout));
|
||||
discoveredSet.addAll(findTempDirs(appName, layout));
|
||||
List<File> discoveredDirs = new ArrayList<>(discoveredSet);
|
||||
|
||||
// Exit if we didn't discover any directories
|
||||
if (discoveredDirs.isEmpty()) {
|
||||
System.out.println("NONE FOUND");
|
||||
return;
|
||||
}
|
||||
|
||||
// Output discovered directories and prompt user
|
||||
File potentialParentDir = null;
|
||||
for (int i = 0; i < discoveredDirs.size(); i++) {
|
||||
File d = discoveredDirs.get(i);
|
||||
File parentDir = d.getParentFile();
|
||||
boolean indent = parentDir.equals(potentialParentDir);
|
||||
System.out.println("%2d)%s %s".formatted(i + 1, indent ? " " : "", d));
|
||||
if (!indent) {
|
||||
potentialParentDir = d;
|
||||
}
|
||||
}
|
||||
System.out.println("*) All");
|
||||
System.out.println("0) Exit");
|
||||
System.out.print("Enter a directory to delete: ");
|
||||
|
||||
// Get user choice and delete
|
||||
String choice = null;
|
||||
try (Scanner scanner = new Scanner(System.in)){
|
||||
List<File> failures = new ArrayList<>();
|
||||
choice = scanner.nextLine().trim();
|
||||
switch (choice) {
|
||||
case "0":
|
||||
System.out.println("Exiting...");
|
||||
return;
|
||||
case "*":
|
||||
for (File dir : discoveredDirs) {
|
||||
if (dir.isDirectory()) {
|
||||
if (!FileUtilities.deleteDir(dir)) {
|
||||
failures.add(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
File dir = discoveredDirs.get(Integer.parseInt(choice) - 1);
|
||||
if (!FileUtilities.deleteDir(dir)) {
|
||||
failures.add(dir);
|
||||
}
|
||||
}
|
||||
System.out.println(failures.isEmpty() ? "SUCCESS" : "Failed to delete:");
|
||||
failures.forEach(dir -> System.out.println(" " + dir));
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
// User likely hit ctrl+c to exit
|
||||
}
|
||||
catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
System.out.println("Invalid entry: \"" + choice + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user settings directories
|
||||
*
|
||||
* @param appName The name of the application
|
||||
* @param layout The layout
|
||||
* @return A {@link Set} of discovered user settings directories, ordered such that
|
||||
* parent directories are directly followed by their subdirectories, if applicable
|
||||
* @see ApplicationUtilities#getDefaultUserSettingsDir(ApplicationProperties, ResourceFile)
|
||||
* @see ApplicationUtilities#getLegacyUserSettingsDir(ApplicationProperties, ResourceFile)
|
||||
*/
|
||||
private Set<File> findSettingsDirs(String appName, ApplicationLayout layout) {
|
||||
Set<File> discoveredDirs = new LinkedHashSet<>();
|
||||
appName = appName.toLowerCase();
|
||||
String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName;
|
||||
|
||||
// Legacy default settings directory
|
||||
getDirFromProperty("user.home", "." + appName).ifPresent(dir -> {
|
||||
discoveredDirs.add(dir);
|
||||
discoveredDirs.addAll(getSubdirs(dir));
|
||||
});
|
||||
|
||||
// Current default settings directory
|
||||
File settingsDir = layout.getUserSettingsDir();
|
||||
File settingsParentDir = settingsDir.getParentFile();
|
||||
if (settingsParentDir != null && (settingsParentDir.getName().equals(appName) ||
|
||||
settingsParentDir.getName().equals(userNameAndAppName))) {
|
||||
discoveredDirs.add(settingsParentDir);
|
||||
discoveredDirs.addAll(getSubdirs(settingsParentDir));
|
||||
}
|
||||
|
||||
// Application system property override (likely not set for AppCleaner)
|
||||
getDirFromProperty(PROPERTY_SETTINGS_DIR, appName).ifPresent(dir -> {
|
||||
discoveredDirs.add(dir);
|
||||
discoveredDirs.addAll(getSubdirs(dir));
|
||||
});
|
||||
getDirFromProperty(PROPERTY_SETTINGS_DIR, userNameAndAppName).ifPresent(dir -> {
|
||||
discoveredDirs.add(dir);
|
||||
discoveredDirs.addAll(getSubdirs(dir));
|
||||
});
|
||||
|
||||
// XDG environment variable override
|
||||
getDirFromEnv(XDG_CONFIG_HOME, appName).ifPresent(dir -> {
|
||||
discoveredDirs.add(dir);
|
||||
discoveredDirs.addAll(getSubdirs(dir));
|
||||
});
|
||||
getDirFromEnv(XDG_CONFIG_HOME, userNameAndAppName).ifPresent(dir -> {
|
||||
discoveredDirs.add(dir);
|
||||
discoveredDirs.addAll(getSubdirs(dir));
|
||||
});
|
||||
|
||||
return discoveredDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user cache directories
|
||||
*
|
||||
* @param appName The name of the application
|
||||
* @param layout The layout
|
||||
* @return A {@link Set} of discovered user cache directories, ordered such that
|
||||
* parent directories are directly followed by their subdirectories, if applicable
|
||||
* @see ApplicationUtilities#getDefaultUserCacheDir(ApplicationProperties)
|
||||
*/
|
||||
private Set<File> findCacheDirs(String appName, ApplicationLayout layout) {
|
||||
Set<File> discoveredDirs = new LinkedHashSet<>();
|
||||
|
||||
// Legacy cache directories
|
||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM.equals(OperatingSystem.WINDOWS)) {
|
||||
getDirFromEnv("LOCALAPPDATA", appName).ifPresent(discoveredDirs::add);
|
||||
}
|
||||
else {
|
||||
String legacyName = SystemUtilities.getUserName() + "-" + appName;
|
||||
getDirFromProperty("java.io.tmpdir", legacyName).ifPresent(discoveredDirs::add);
|
||||
}
|
||||
|
||||
// Newer cache directories always use a lowercase application name
|
||||
appName = appName.toLowerCase();
|
||||
String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName;
|
||||
|
||||
// Current cache directories
|
||||
File cacheDir = layout.getUserCacheDir();
|
||||
if (cacheDir != null && cacheDir.isDirectory()) {
|
||||
discoveredDirs.add(cacheDir);
|
||||
}
|
||||
|
||||
// Application system property override (likely not set for AppCleaner)
|
||||
getDirFromProperty(PROPERTY_CACHE_DIR, appName).ifPresent(discoveredDirs::add);
|
||||
getDirFromProperty(PROPERTY_CACHE_DIR, userNameAndAppName).ifPresent(discoveredDirs::add);
|
||||
|
||||
// XDG environment variable override
|
||||
getDirFromEnv(XDG_CACHE_HOME, appName).ifPresent(discoveredDirs::add);
|
||||
getDirFromEnv(XDG_CACHE_HOME, userNameAndAppName).ifPresent(discoveredDirs::add);
|
||||
|
||||
return discoveredDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user temp directories
|
||||
*
|
||||
* @param appName The name of the application
|
||||
* @param layout The layout
|
||||
* @return A {@link Set} of discovered user temp directories, ordered such that
|
||||
* parent directories are directly followed by their subdirectories, if applicable
|
||||
* @see ApplicationUtilities#getDefaultUserTempDir(String)
|
||||
*/
|
||||
private Set<File> findTempDirs(String appName, ApplicationLayout layout) {
|
||||
Set<File> discoveredDirs = new LinkedHashSet<>();
|
||||
|
||||
// Legacy temp directories
|
||||
String legacyName = SystemUtilities.getUserName() + "-" + appName;
|
||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM.equals(OperatingSystem.WINDOWS)) {
|
||||
getDirFromEnv("TEMP", legacyName).ifPresent(discoveredDirs::add);
|
||||
}
|
||||
else {
|
||||
getDirFromProperty("java.io.tmpdir", legacyName).ifPresent(discoveredDirs::add);
|
||||
}
|
||||
|
||||
// Newer temp directories always use a lowercase application name
|
||||
appName = appName.toLowerCase();
|
||||
String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName;
|
||||
|
||||
// Current temp directories
|
||||
File tempDir = layout.getUserTempDir();
|
||||
if (tempDir != null && tempDir.isDirectory()) {
|
||||
discoveredDirs.add(tempDir);
|
||||
}
|
||||
|
||||
// Application system property override (likely not set for AppCleaner)
|
||||
getDirFromProperty(PROPERTY_TEMP_DIR, appName).ifPresent(discoveredDirs::add);
|
||||
getDirFromProperty(PROPERTY_TEMP_DIR, userNameAndAppName).ifPresent(discoveredDirs::add);
|
||||
|
||||
// XDG environment variable override
|
||||
getDirFromEnv(XDG_RUNTIME_DIR, appName).ifPresent(discoveredDirs::add);
|
||||
getDirFromEnv(XDG_RUNTIME_DIR, userNameAndAppName).ifPresent(discoveredDirs::add);
|
||||
|
||||
return discoveredDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subdirectory of the given name found within the directory specified by the given
|
||||
* system property
|
||||
*
|
||||
* @param propertyName The name of the system property
|
||||
* @param subdirName The name of the subdirectory within the directory specified by the given
|
||||
* system property
|
||||
* @return The subdirectory of the given name found within the directory specified by the given
|
||||
* systemProperty
|
||||
*/
|
||||
private Optional<File> getDirFromProperty(String propertyName, String subdirName) {
|
||||
String path = System.getProperty(propertyName, "").trim();
|
||||
if (!path.isEmpty()) {
|
||||
File dir = new File(path, subdirName);
|
||||
if (dir.isDirectory()) {
|
||||
return Optional.of(dir);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subdirectory of the given name found within the directory specified by the given
|
||||
* environment variable
|
||||
*
|
||||
* @param envName The name of the environment variable
|
||||
* @param subdirName The name of the subdirectory within the directory specified by the given
|
||||
* environment variable
|
||||
* @return The subdirectory of the given name found within the directory specified by the given
|
||||
* environment variable
|
||||
*/
|
||||
private Optional<File> getDirFromEnv(String envName, String subdirName) {
|
||||
String path = System.getenv(envName);
|
||||
if (path != null && !path.isBlank()) {
|
||||
File dir = new File(path, subdirName);
|
||||
if (dir.isDirectory()) {
|
||||
return Optional.of(dir);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the direct sub-directories of the given directory (non-recursive)
|
||||
*
|
||||
* @param dir The directory to get the sub-directories of
|
||||
* @return The direct sub-directories of the given directory
|
||||
*/
|
||||
private List<File> getSubdirs(File dir) {
|
||||
File[] listing = dir.listFiles(File::isDirectory);
|
||||
return listing != null ? Arrays.asList(listing) : List.of();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,11 +15,11 @@
|
|||
*/
|
||||
package utility.application;
|
||||
|
||||
import ghidra.framework.PluggableServiceRegistry;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import utilities.util.FileUtilities;
|
||||
import ghidra.framework.PluggableServiceRegistry;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ApplicationSettings {
|
||||
static {
|
||||
|
@ -46,6 +46,13 @@ public class ApplicationSettings {
|
|||
* application version.
|
||||
*/
|
||||
protected File doGetUserApplicationSettingsDirectory() {
|
||||
return FileUtilities.createTempDirectory("application.settings_");
|
||||
try {
|
||||
return ApplicationUtilities.getDefaultUserTempDir("application.settings");
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(ApplicationSettings.class, "Error creating application.settings directory",
|
||||
e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,28 @@ import generic.jar.ResourceFile;
|
|||
import ghidra.framework.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
* Utility class for default application things.
|
||||
*/
|
||||
public class ApplicationUtilities {
|
||||
|
||||
/**
|
||||
* Name of system property used to override the location of the user temporary directory
|
||||
*/
|
||||
public static final String PROPERTY_TEMP_DIR = "application.tempdir";
|
||||
|
||||
/**
|
||||
* Name of system property used to override the location of the user cache directory
|
||||
*/
|
||||
public static final String PROPERTY_CACHE_DIR = "application.cachedir";
|
||||
|
||||
/**
|
||||
* Name of system property used to override the location of the user settings directory
|
||||
*/
|
||||
public static final String PROPERTY_SETTINGS_DIR = "application.settingsdir";
|
||||
|
||||
/**
|
||||
* Searches for default application root directories.
|
||||
*
|
||||
|
@ -137,85 +153,145 @@ public class ApplicationUtilities {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the default application's user temp directory.
|
||||
* Gets the application's default user temp directory.
|
||||
* <p>
|
||||
* NOTE: This method does not create the directory.
|
||||
*
|
||||
* @param applicationProperties The application properties.
|
||||
* @return The default application's user temp directory.
|
||||
* @throws FileNotFoundException if the user temp directory could not be determined.
|
||||
* @param applicationName The application name.
|
||||
* @return The application's default user temp directory. The returned {@link File} will
|
||||
* represent an absolute path.
|
||||
* @throws FileNotFoundException if the absolute path of the user temp directory could not be
|
||||
* determined.
|
||||
*/
|
||||
public static File getDefaultUserTempDir(ApplicationProperties applicationProperties)
|
||||
throws FileNotFoundException {
|
||||
String tmpdir = System.getProperty("java.io.tmpdir");
|
||||
if (tmpdir == null || tmpdir.isEmpty()) {
|
||||
throw new FileNotFoundException("System property \"java.io.tmpdir\" is not set!");
|
||||
public static File getDefaultUserTempDir(String applicationName) throws FileNotFoundException {
|
||||
|
||||
String appName = applicationName.toLowerCase();
|
||||
|
||||
// Look for Ghidra-specific system property
|
||||
File tempOverrideDir = getSystemPropertyFile(PROPERTY_TEMP_DIR, false);
|
||||
if (tempOverrideDir != null) {
|
||||
return new File(tempOverrideDir, getUserSpecificDirName(tempOverrideDir, appName));
|
||||
}
|
||||
return new File(tmpdir,
|
||||
SystemUtilities.getUserName() + "-" + applicationProperties.getApplicationName());
|
||||
|
||||
// Look for XDG environment variable
|
||||
File xdgRuntimeDir = getEnvFile(XdgUtils.XDG_RUNTIME_DIR, false);
|
||||
if (xdgRuntimeDir != null) {
|
||||
return new File(xdgRuntimeDir, getUserSpecificDirName(xdgRuntimeDir, appName));
|
||||
}
|
||||
|
||||
File javaTmpDir = getJavaTmpDir();
|
||||
return new File(javaTmpDir, getUserSpecificDirName(javaTmpDir, appName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default application's user cache directory.
|
||||
* Gets the application's default user cache directory.
|
||||
* <p>
|
||||
* NOTE: This method does not create the directory.
|
||||
*
|
||||
* @param applicationProperties The application properties.
|
||||
* @return The default application's user cache directory.
|
||||
* @throws FileNotFoundException if the user cache directory could not be determined.
|
||||
* @return The application's default user cache directory. The returned {@link File} will
|
||||
* represent an absolute path.
|
||||
* @throws FileNotFoundException if the absolute path of the user cache directory could not be
|
||||
* determined.
|
||||
*/
|
||||
public static File getDefaultUserCacheDir(ApplicationProperties applicationProperties)
|
||||
throws FileNotFoundException {
|
||||
|
||||
// Look for preset cache directory
|
||||
String cachedir = System.getProperty("application.cachedir", "").trim();
|
||||
if (!cachedir.isEmpty()) {
|
||||
return new File(cachedir,
|
||||
SystemUtilities.getUserName() + "-" + applicationProperties.getApplicationName());
|
||||
String appName = applicationProperties.getApplicationName().toLowerCase();
|
||||
|
||||
// Look for Ghidra-specific system property
|
||||
File cacheOverrideDir = getSystemPropertyFile(PROPERTY_CACHE_DIR, false);
|
||||
if (cacheOverrideDir != null) {
|
||||
return new File(cacheOverrideDir, getUserSpecificDirName(cacheOverrideDir, appName));
|
||||
}
|
||||
|
||||
// Handle Windows specially
|
||||
if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) {
|
||||
File localAppDataDir = null;
|
||||
String localAppDataDirPath = System.getenv("LOCALAPPDATA"); // e.g., /Users/myname/AppData/Local
|
||||
if (localAppDataDirPath != null && !localAppDataDirPath.isEmpty()) {
|
||||
localAppDataDir = new File(localAppDataDirPath);
|
||||
}
|
||||
else {
|
||||
String userHome = System.getProperty("user.home");
|
||||
if (userHome != null) {
|
||||
localAppDataDir = new File(userHome, "AppData\\Local");
|
||||
if (!localAppDataDir.isDirectory()) {
|
||||
localAppDataDir = new File(userHome, "Local Settings");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (localAppDataDir != null && localAppDataDir.isDirectory()) {
|
||||
return new File(localAppDataDir, applicationProperties.getApplicationName());
|
||||
}
|
||||
// Look for XDG environment variable
|
||||
File xdgCacheHomeDir = getEnvFile(XdgUtils.XDG_CACHE_HOME, false);
|
||||
if (xdgCacheHomeDir != null) {
|
||||
return new File(xdgCacheHomeDir, getUserSpecificDirName(xdgCacheHomeDir, appName));
|
||||
}
|
||||
|
||||
// Use user temp directory if platform specific scheme does not exist above or it failed
|
||||
return getDefaultUserTempDir(applicationProperties);
|
||||
|
||||
// Use platform-specific default location
|
||||
String userDirName = SystemUtilities.getUserName() + "-" + appName;
|
||||
return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||
case WINDOWS -> new File(getEnvFile("LOCALAPPDATA", true), appName);
|
||||
case LINUX -> new File("/var/tmp/" + userDirName);
|
||||
case MAC_OS_X -> new File("/var/tmp/" + userDirName);
|
||||
default -> throw new FileNotFoundException(
|
||||
"Failed to find the user cache directory: Unsupported operating system.");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default application's user settings directory.
|
||||
* Gets the application's default user settings directory.
|
||||
* <p>
|
||||
* NOTE: This method does not create the directory.
|
||||
*
|
||||
* @param applicationProperties The application properties.
|
||||
* @param installationDirectory The application installation directory.
|
||||
* @return The application's user settings directory.
|
||||
* @throws FileNotFoundException if the user settings directory could not be determined.
|
||||
* @return The application's default user settings directory. The returned {@link File} will
|
||||
* represent an absolute path.
|
||||
* @throws FileNotFoundException if the absolute path of the user settings directory could not
|
||||
* be determined.
|
||||
*/
|
||||
public static File getDefaultUserSettingsDir(ApplicationProperties applicationProperties,
|
||||
ResourceFile installationDirectory) throws FileNotFoundException {
|
||||
|
||||
String homedir = System.getProperty("user.home");
|
||||
if (homedir == null || homedir.isEmpty()) {
|
||||
throw new FileNotFoundException("System property \"user.home\" is not set!");
|
||||
String appName = applicationProperties.getApplicationName().toLowerCase();
|
||||
ApplicationIdentifier applicationIdentifier =
|
||||
new ApplicationIdentifier(applicationProperties);
|
||||
String versionedName = applicationIdentifier.toString();
|
||||
if (SystemUtilities.isInDevelopmentMode()) {
|
||||
// Add the application's installation directory name to this variable, so that each
|
||||
// branch's project user directory is unique.
|
||||
versionedName += "_location_" + installationDirectory.getName();
|
||||
}
|
||||
|
||||
// Look for Ghidra-specific system property
|
||||
File settingsOverrideDir = getSystemPropertyFile(PROPERTY_SETTINGS_DIR, false);
|
||||
if (settingsOverrideDir != null) {
|
||||
return new File(settingsOverrideDir,
|
||||
getUserSpecificDirName(settingsOverrideDir, appName) + "/" + versionedName);
|
||||
}
|
||||
|
||||
// Look for XDG environment variable
|
||||
File xdgConfigHomeDir = getEnvFile(XdgUtils.XDG_CONFIG_HOME, false);
|
||||
if (xdgConfigHomeDir != null) {
|
||||
return new File(xdgConfigHomeDir,
|
||||
getUserSpecificDirName(xdgConfigHomeDir, appName) + "/" + versionedName);
|
||||
}
|
||||
|
||||
File userHomeDir = getJavaUserHomeDir();
|
||||
String versionedSubdir = appName + "/" + versionedName;
|
||||
return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) {
|
||||
case WINDOWS -> new File(getEnvFile("APPDATA", true), versionedSubdir);
|
||||
case LINUX -> new File(userHomeDir, ".config/" + versionedSubdir);
|
||||
case MAC_OS_X -> new File(userHomeDir, "Library/" + versionedSubdir);
|
||||
default -> throw new FileNotFoundException(
|
||||
"Failed to find the user settings directory: Unsupported operating system.");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application's legacy (pre-Ghida 11.1) user settings directory.
|
||||
* <p>
|
||||
* NOTE: This method does not create the directory.
|
||||
*
|
||||
* @param applicationProperties The application properties.
|
||||
* @param installationDirectory The application installation directory.
|
||||
* @return The application's legacy user settings directory. The returned {@link File} will
|
||||
* represent an absolute path.
|
||||
* @throws FileNotFoundException if the absolute path of the legacy user settings directory
|
||||
* could not be determined.
|
||||
*/
|
||||
public static File getLegacyUserSettingsDir(ApplicationProperties applicationProperties,
|
||||
ResourceFile installationDirectory) throws FileNotFoundException {
|
||||
|
||||
ApplicationIdentifier applicationIdentifier =
|
||||
new ApplicationIdentifier(applicationProperties);
|
||||
|
||||
File userSettingsParentDir =
|
||||
new File(homedir, "." + applicationIdentifier.getApplicationName());
|
||||
new File(getJavaUserHomeDir(), "." + applicationIdentifier.getApplicationName());
|
||||
|
||||
String userSettingsDirName = "." + applicationIdentifier;
|
||||
|
||||
|
@ -227,4 +303,108 @@ public class ApplicationUtilities {
|
|||
|
||||
return new File(userSettingsParentDir, userSettingsDirName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Java's temporary directory in absolute form
|
||||
*
|
||||
* @return Java's temporary directory in absolute form
|
||||
* @throws FileNotFoundException if Java's temporary directory is not defined or it is not an
|
||||
* absolute path
|
||||
*/
|
||||
private static File getJavaTmpDir() throws FileNotFoundException {
|
||||
return getSystemPropertyFile("java.io.tmpdir", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Java's user home directory in absolute form
|
||||
*
|
||||
* @return Java's user home directory in absolute form
|
||||
* @throws FileNotFoundException if Java's user home directory is not defined or it is not an
|
||||
* absolute path
|
||||
*/
|
||||
private static File getJavaUserHomeDir() throws FileNotFoundException {
|
||||
return getSystemPropertyFile("user.home", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the absolute form {@link File} value of the system property by the given name
|
||||
*
|
||||
* @param name The system property name
|
||||
* @param required True if given system property is required to be set; otherwise, false
|
||||
* @return The absolute form {@link File} value of the system property by the given name, or
|
||||
* null if it isn't set
|
||||
* @throws FileNotFoundException if the property value was not an absolute path, or if it is
|
||||
* required and not set
|
||||
*/
|
||||
private static File getSystemPropertyFile(String name, boolean required)
|
||||
throws FileNotFoundException {
|
||||
String path = System.getProperty(name);
|
||||
if (path == null || path.isBlank()) {
|
||||
if (required) {
|
||||
throw new FileNotFoundException(
|
||||
"Required system property \"%s\" is not set!".formatted(name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
path = path.trim();
|
||||
File file = new File(path);
|
||||
if (!file.isAbsolute()) {
|
||||
throw new FileNotFoundException(
|
||||
"System property \"%s\" is not an absolute path: \"%s\"".formatted(name, path));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the absolute form {@link File} value of the environment variable by the given name
|
||||
*
|
||||
* @param name The environment variable name
|
||||
* @param required True if the given environment variable is required to be set; otherwise,
|
||||
* false
|
||||
* @return The absolute form {@link File} value of the environment variable by the given name,
|
||||
* or null if it isn't set
|
||||
* @throws FileNotFoundException if the property value was not an absolute path, or if it is
|
||||
* required and not set
|
||||
*/
|
||||
private static File getEnvFile(String name, boolean required) throws FileNotFoundException {
|
||||
String path = System.getenv(name);
|
||||
if (path == null || path.isBlank()) {
|
||||
if (required) {
|
||||
throw new FileNotFoundException(
|
||||
"Required environment variable \"%s\" is not set!".formatted(name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
path = path.trim();
|
||||
File file = new File(path);
|
||||
if (!file.isAbsolute()) {
|
||||
throw new FileNotFoundException(
|
||||
"Environment variable \"%s\" is not an absolute path: \"%s\"".formatted(name,
|
||||
path));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a directory name that can be used to create a user-specific sub-directory in
|
||||
* {@code parentDir}. If the {@code parentDir} is contained within the user's home directory,
|
||||
* the given {@code appName} can simply be used since it will live in a user-specific location.
|
||||
* Otherwise, the user's name will get prepended to the {@code appName} so it does not collide
|
||||
* with other users' directories in the shared directory space.
|
||||
|
||||
* @param parentDir The parent directory where we'd like to create a user-specific sub-directory
|
||||
* @param appName The application name
|
||||
* @return A directory name that can be used to create a user-specific sub-directory in
|
||||
* {@code parentDir}.
|
||||
* @throws FileNotFoundException if Java's user home directory is not defined or it is not an
|
||||
* absolute path
|
||||
*/
|
||||
private static String getUserSpecificDirName(File parentDir, String appName)
|
||||
throws FileNotFoundException {
|
||||
String userSpecificDirName = appName;
|
||||
if (!FileUtilities.isPathContainedWithin(getJavaUserHomeDir(), parentDir)) {
|
||||
userSpecificDirName = SystemUtilities.getUserName() + "-" + appName;
|
||||
}
|
||||
return userSpecificDirName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package utility.application;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -32,9 +32,9 @@ public class DummyApplicationLayout extends ApplicationLayout {
|
|||
/**
|
||||
* Constructs a new dummy application layout object.
|
||||
* @param name the application name
|
||||
* @throws FileNotFoundException if there was a problem getting a user directory.
|
||||
* @throws IOException if there was a problem getting a user directory.
|
||||
*/
|
||||
public DummyApplicationLayout(String name) throws FileNotFoundException {
|
||||
public DummyApplicationLayout(String name) throws IOException {
|
||||
|
||||
// Application properties
|
||||
applicationProperties = new ApplicationProperties(name);
|
||||
|
@ -48,7 +48,8 @@ public class DummyApplicationLayout extends ApplicationLayout {
|
|||
applicationRootDirs.add(cwd);
|
||||
|
||||
// User directories
|
||||
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
|
||||
userTempDir =
|
||||
ApplicationUtilities.getDefaultUserTempDir(applicationProperties.getApplicationName());
|
||||
|
||||
extensionInstallationDirs = Collections.emptyList();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package utility.application;
|
||||
|
||||
/**
|
||||
* Class to support the "XDG Base Directory Specification"
|
||||
* <p>
|
||||
* Based off version 0.8
|
||||
*
|
||||
* @see <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.8.html">basedir-spec-0.8.html</a>
|
||||
*/
|
||||
public class XdgUtils {
|
||||
|
||||
/**
|
||||
* $XDG_DATA_HOME defines the base directory relative to which user-specific data files should
|
||||
* be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
|
||||
* $HOME/.local/share should be used.
|
||||
*/
|
||||
public static final String XDG_DATA_HOME = "XDG_DATA_HOME";
|
||||
|
||||
/**
|
||||
* $XDG_CONFIG_HOME defines the base directory relative to which user-specific configuration
|
||||
* files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to
|
||||
* $HOME/.config should be used.
|
||||
*/
|
||||
public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
|
||||
|
||||
/**
|
||||
* $XDG_STATE_HOME defines the base directory relative to which user-specific state files should
|
||||
* be stored. If $XDG_STATE_HOME is either not set or empty, a default equal to
|
||||
* $HOME/.local/state should be used.
|
||||
*/
|
||||
public static final String XDG_STATE_HOME = "XDG_STATE_HOME";
|
||||
|
||||
/**
|
||||
* $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data
|
||||
* files in addition to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS
|
||||
* should be separated with a colon ':'.
|
||||
*/
|
||||
public static final String XDG_DATA_DIRS = "XDG_DATA_DIRS";
|
||||
|
||||
/**
|
||||
* $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for
|
||||
* configuration files in addition to the $XDG_CONFIG_HOME base directory. The directories in
|
||||
* $XDG_CONFIG_DIRS should be separated with a colon ':'.
|
||||
*/
|
||||
public static final String XDG_CONFIG_DIRS = "XDG_CONFIG_DIRS";
|
||||
|
||||
/**
|
||||
* $XDG_CACHE_HOME defines the base directory relative to which user-specific non-essential
|
||||
* data files should be stored. If $XDG_CACHE_HOME is either not set or empty, a default equal
|
||||
* to $HOME/.cache should be used.
|
||||
*/
|
||||
public static final String XDG_CACHE_HOME = "XDG_CACHE_HOME";
|
||||
|
||||
/**
|
||||
* $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential
|
||||
* runtime files and other file objects (such as sockets, named pipes, ...) should be stored.
|
||||
* The directory MUST be owned by the user, and he MUST be the only one having read and write
|
||||
* access to it. Its Unix access mode MUST be 0700.
|
||||
*/
|
||||
public static final String XDG_RUNTIME_DIR = "XDG_RUNTIME_DIR";
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue