add opt-in xdg application directory support
What changed, and why it matters
This commit changes where Sparrow Wallet stores its files on macOS and Linux to optionally follow the XDG Base Directory Specification. It is a directory-layout migration, not a fix for a vulnerability. The change could create minor security side effects if old files remain in the previous home directory or if symlink handling during network-folder migration goes wrong, but the commit itself does not introduce an obvious exploit.
Treat as a normal feature/refactor commit. Review the migration logic for symlink races and ensure owner-only directory creation is preserved for all new XDG directories. Verify that wallet files and config are not left world-readable during migration. No urgent security patch is indicated by the diff alone.
Security signals we found
Directory migration logic with symlink creation and fallback search paths
Instance lock pointer now searched in both new XDG state home and legacy default home
Network directory rename logic creates/updates symlinks and may leave stale symlinks
No change to file permissions, encryption, or authentication
No vendor statement of security relevance in commit or supplied references
Evidence from the diff
The patch replaces the single ~/.sparrow home directory with opt-in XDG-style directories (config, data, state, cache). It updates Storage, Config, Hwi, Tor, Instance, AppController, and SparrowWallet to use ApplicationDir-based helpers. It also preserves backward compatibility by searching both the new state pointer and the old default home for instance lock files, and by maintaining symlinks for renamed network directories. No cryptographic, network, or access-control logic is changed.
Changed components
src/main/java/com/sparrowwallet/sparrow/io/Storage.javasrc/main/java/com/sparrowwallet/sparrow/io/Config.javasrc/main/java/com/sparrowwallet/sparrow/io/Hwi.javasrc/main/java/com/sparrowwallet/sparrow/net/Tor.javasrc/main/java/com/sparrowwallet/sparrow/instance/Instance.javasrc/main/java/com/sparrowwallet/sparrow/AppController.javasrc/main/java/com/sparrowwallet/sparrow/SparrowWallet.javaREADME.mdInspect captured patch +177 / −82
diff --git a/README.md b/README.md
index 3e5d60c..9285a63 100644
--- a/README.md
+++ b/README.md
@@ -81,12 +81,24 @@ When not explicitly configured using the command line argument above, Sparrow st
| Platform | Location |
|----------| -------- |
-| OSX | ~/.sparrow |
+| macOS | ~/.sparrow |
| Linux | ~/.sparrow |
| Windows | %APPDATA%/Sparrow |
Testnet3, testnet4, regtest and signet configurations (along with their wallets) are stored in subfolders to allow easy switching between networks.
+On macOS and Linux, Sparrow also supports the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/).
+This is opt in: for each category below, if the corresponding directory already exists, Sparrow uses it, otherwise it continues to use the home folder above. Categories are resolved independently, so files can be moved across one at a time.
+
+| Category | Location | Contents |
+|----------| -------- |---------------------------------------|
+| Config | `$XDG_CONFIG_HOME/sparrow` (default `~/.config/sparrow`) | `config`, `network-*` markers |
+| Data | `$XDG_DATA_HOME/sparrow` (default `~/.local/share/sparrow`) | `wallets`, `certs`, `lark` |
+| State | `$XDG_STATE_HOME/sparrow` (default `~/.local/state/sparrow`) | `sparrow.log`, `tor/work`, lock files |
+| Cache | `$XDG_CACHE_HOME/sparrow` (default `~/.cache/sparrow`) | `tor/cache` |
+
+Specifying a home folder with the `-d` argument disables XDG resolution entirely, and stores all files in the given folder.
+
## Reporting Issues
Please use the [Issues](https://github.com/sparrowwallet/sparrow/issues) tab above to report an issue. If possible, look in the sparrow.log file in the configuration directory for information helpful in debugging.
diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java
index 1ccf222..4ff89e4 100644
--- a/src/main/java/com/sparrowwallet/sparrow/AppController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java
@@ -524,7 +524,7 @@ public class AppController implements Initializable {
}
public void showLogFile(ActionEvent event) throws IOException {
- File logFile = new File(Storage.getSparrowHome(), "sparrow.log");
+ File logFile = new File(Storage.getStateHome(), "sparrow.log");
if(logFile.exists()) {
AppServices.get().getApplication().getHostServices().showDocument(logFile.toPath().toUri().toString());
} else {
@@ -1040,7 +1040,7 @@ public class AppController implements Initializable {
Stage window = new Stage();
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Choose Sparrow Home Folder");
- directoryChooser.setInitialDirectory(initialDir == null || !initialDir.exists() ? Storage.getSparrowHome() : initialDir);
+ directoryChooser.setInitialDirectory(initialDir == null || !initialDir.exists() ? Storage.getDefaultHome() : initialDir);
File newHome = directoryChooser.showDialog(window);
if(newHome != null) {
diff --git a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java
index 4f2ba15..5b72546 100644
--- a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java
+++ b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java
@@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow;
import com.beust.jcommander.JCommander;
+import com.sparrowwallet.drongo.ApplicationDir;
import com.sparrowwallet.drongo.Drongo;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.sparrow.io.Storage;
@@ -20,7 +21,7 @@ public class SparrowWallet {
public static final String APP_NAME = "Sparrow";
public static final String APP_VERSION = "2.5.3";
public static final String APP_VERSION_SUFFIX = "";
- public static final String APP_HOME_PROPERTY = "sparrow.home";
+ public static final String APP_HOME_PROPERTY = ApplicationDir.getHomeProperty(APP_NAME);
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
public static final String JPACKAGE_APP_PATH = "jpackage.app-path";
@@ -71,17 +72,19 @@ public class SparrowWallet {
}
}
- File testnetFlag = new File(Storage.getSparrowHome(), "network-" + Network.TESTNET.getName());
+ Storage.logApplicationDirs();
+
+ File testnetFlag = new File(Storage.getConfigHome(), "network-" + Network.TESTNET.getName());
if(testnetFlag.exists()) {
Network.set(Network.TESTNET);
}
- File testnet4Flag = new File(Storage.getSparrowHome(), "network-" + Network.TESTNET4.getName());
+ File testnet4Flag = new File(Storage.getConfigHome(), "network-" + Network.TESTNET4.getName());
if(testnet4Flag.exists()) {
Network.set(Network.TESTNET4);
}
- File signetFlag = new File(Storage.getSparrowHome(), "network-" + Network.SIGNET.getName());
+ File signetFlag = new File(Storage.getConfigHome(), "network-" + Network.SIGNET.getName());
if(signetFlag.exists()) {
Network.set(Network.SIGNET);
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java b/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java
index a5d6556..1b0c91a 100644
--- a/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java
+++ b/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java
@@ -20,6 +20,7 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
public abstract class Instance {
@@ -31,6 +32,8 @@ public abstract class Instance {
private Selector selector;
private ServerSocketChannel serverChannel;
+ private Path acquiredLockFile;
+ private Path acquiredLockFilePointer;
public Instance(final String applicationId) {
this(applicationId, true);
@@ -65,6 +68,7 @@ public abstract class Instance {
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
lockFile.toFile().deleteOnExit();
+ acquiredLockFile = lockFile;
} catch(Exception e) {
throw new InstanceException("Could not open UNIX socket lock file for instance at " + lockFile.toAbsolutePath(), e);
}
@@ -145,26 +149,27 @@ public abstract class Instance {
private Path getLockFile(boolean findExisting) {
if(findExisting) {
- Path pointer = getUserLockFilePointer();
- try {
- if(pointer != null && Files.exists(pointer)) {
- if(Files.isSymbolicLink(pointer)) {
- return Files.readSymbolicLink(pointer);
- } else {
- Path lockFile = Path.of(Files.readString(pointer, StandardCharsets.UTF_8));
- if(Files.exists(lockFile)) {
- return lockFile;
+ for(Path pointer : getUserLockFilePointers()) {
+ try {
+ if(Files.exists(pointer)) {
+ if(Files.isSymbolicLink(pointer)) {
+ return Files.readSymbolicLink(pointer);
+ } else {
+ Path lockFile = Path.of(Files.readString(pointer, StandardCharsets.UTF_8));
+ if(Files.exists(lockFile)) {
+ return lockFile;
+ }
}
}
+ } catch(IOException e) {
+ log.warn("Could not find lock file at " + pointer.toAbsolutePath());
+ } catch(Exception e) {
+ //ignore
}
- } catch(IOException e) {
- log.warn("Could not find lock file at " + pointer.toAbsolutePath());
- } catch(Exception e) {
- //ignore
}
}
- return Storage.getSparrowDir().toPath().resolve(applicationId + ".lock");
+ return Storage.getStateDir().toPath().resolve(applicationId + ".lock");
}
private void createSymlink(Path lockFile) {
@@ -173,6 +178,7 @@ public abstract class Instance {
if(pointer != null && !Files.exists(pointer, LinkOption.NOFOLLOW_LINKS)) {
Files.createSymbolicLink(pointer, lockFile);
pointer.toFile().deleteOnExit();
+ acquiredLockFilePointer = pointer;
}
} catch(IOException e) {
log.debug("Could not create symlink " + pointer.toAbsolutePath() + " to lockFile at " + lockFile.toAbsolutePath() + ", writing as normal file", e);
@@ -180,6 +186,7 @@ public abstract class Instance {
try {
Files.writeString(pointer, lockFile.toAbsolutePath().toString(), StandardCharsets.UTF_8);
pointer.toFile().deleteOnExit();
+ acquiredLockFilePointer = pointer;
} catch(IOException ex) {
log.warn("Could not create pointer " + pointer.toAbsolutePath() + " to lockFile at " + lockFile.toAbsolutePath(), ex);
}
@@ -188,24 +195,44 @@ public abstract class Instance {
}
}
+ /**
+ * Returns the location this instance writes its pointer to, ignoring any configured home so that instances started with
+ * and without one still find each other.
+ */
private Path getUserLockFilePointer() {
if(Boolean.parseBoolean(System.getenv(LINK_ENV_PROPERTY))) {
return null;
}
try {
- File sparrowHome = Storage.getSparrowHome(true);
- if(!sparrowHome.exists()) {
- Storage.createOwnerOnlyDirectory(sparrowHome);
- sparrowHome.deleteOnExit(); //Will only delete on exit if empty
+ File stateHome = Storage.getStateHome(true);
+ if(!stateHome.exists()) {
+ Storage.createOwnerOnlyDirectory(stateHome);
+ stateHome.deleteOnExit(); //Will only delete on exit if empty
}
- return sparrowHome.toPath().resolve(applicationId + ".default");
+ return stateHome.toPath().resolve(applicationId + ".default");
} catch(Exception e) {
return null;
}
}
+ /**
+ * Returns the locations that may hold a pointer to a running instance, without creating any of them.
+ *
+ * An instance started before the state directory was migrated writes its pointer to the default home, so that is searched
+ * as well, ensuring migrating does not hide an instance that is already running.
+ */
+ private List<Path> getUserLockFilePointers() {
+ Path statePointer = getUserLockFilePointer();
+ if(statePointer == null) {
+ return List.of();
+ }
+
+ Path defaultPointer = Storage.getDefaultHome().toPath().resolve(applicationId + ".default");
+ return statePointer.equals(defaultPointer) ? List.of(statePointer) : List.of(statePointer, defaultPointer);
+ }
+
/**
* Free the lock if possible. This is only required to be called from the first instance.
*
@@ -216,10 +243,13 @@ public abstract class Instance {
if(serverChannel != null && serverChannel.isOpen()) {
serverChannel.close();
}
- if(getUserLockFilePointer() != null) {
- Files.deleteIfExists(getUserLockFilePointer());
+ //Free the paths this instance acquired, which are not necessarily those that would be resolved now
+ if(acquiredLockFilePointer != null) {
+ Files.deleteIfExists(acquiredLockFilePointer);
+ }
+ if(acquiredLockFile != null) {
+ Files.deleteIfExists(acquiredLockFile);
}
- Files.deleteIfExists(getLockFile(false));
} catch(Exception e) {
throw new InstanceException(e);
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java
index 1dbb24c..28561ea 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java
@@ -108,8 +108,8 @@ public class Config {
}
private static File getConfigFile() {
- File sparrowDir = Storage.getSparrowDir();
- return new File(sparrowDir, CONFIG_FILENAME);
+ File configDir = Storage.getConfigDir();
+ return new File(configDir, CONFIG_FILENAME);
}
private static Config load() {
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
index d62c77c..fd9fa8d 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java
@@ -269,7 +269,7 @@ public class Hwi {
private static void deleteHwiDir() {
try {
if(OsType.getCurrent() == OsType.MACOS || OsType.getCurrent() == OsType.WINDOWS) {
- File hwiHomeDir = new File(Storage.getSparrowDir(), HWI_HOME_DIR);
+ File hwiHomeDir = new File(Storage.getCacheDir(), HWI_HOME_DIR);
if(hwiHomeDir.exists()) {
IOUtils.deleteDirectory(hwiHomeDir);
}
@@ -544,7 +544,7 @@ public class Hwi {
private BitBoxPairingDialog pairingDialog;
public BitBoxFxNoiseConfig() {
- super(Path.of(Storage.getSparrowHome().getAbsolutePath(), LARK_HOME_DIR, BITBOX_FILENAME).toFile());
+ super(Path.of(Storage.getDataHome().getAbsolutePath(), LARK_HOME_DIR, BITBOX_FILENAME).toFile());
}
@Override
@@ -594,7 +594,7 @@ public class Hwi {
private String deviceInfo;
public TrezorFxNoiseConfig() {
- super(Path.of(Storage.getSparrowHome().getAbsolutePath(), LARK_HOME_DIR, TREZOR_FILENAME).toFile());
+ super(Path.of(Storage.getDataHome().getAbsolutePath(), LARK_HOME_DIR, TREZOR_FILENAME).toFile());
}
@Override
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
index 4856ef4..ea93357 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java
@@ -33,6 +33,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
public class Storage {
private static final Logger log = LoggerFactory.getLogger(Storage.class);
@@ -41,8 +42,6 @@ public class Storage {
private static final DateTimeFormatter BACKUP_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final Pattern DATE_PATTERN = Pattern.compile(".+-([0-9]{14}?).*");
- public static final String SPARROW_DIR = ".sparrow";
- public static final String WINDOWS_SPARROW_DIR = "Sparrow";
public static final String WALLETS_DIR = "wallets";
public static final String WALLETS_BACKUP_DIR = "backup";
public static final String CERTS_DIR = "certs";
@@ -501,7 +500,7 @@ public class Storage {
}
}
if(walletsDir == null) {
- walletsDir = new File(getSparrowDir(), WALLETS_DIR);
+ walletsDir = new File(getDataDir(), WALLETS_DIR);
}
if(!walletsDir.exists()) {
createOwnerOnlyDirectory(walletsDir);
@@ -556,7 +555,7 @@ public class Storage {
}
static File getCertsDir() {
- File certsDir = new File(getSparrowDir(), CERTS_DIR);
+ File certsDir = new File(getDataDir(), CERTS_DIR);
if(!certsDir.exists()) {
createOwnerOnlyDirectory(certsDir);
}
@@ -564,73 +563,123 @@ public class Storage {
return certsDir;
}
- public static File getSparrowDir() {
- File sparrowDir;
+ /**
+ * Returns the network specific directory containing the configuration file.
+ */
+ public static File getConfigDir() {
+ return getNetworkDir(getConfigHome());
+ }
+
+ /**
+ * Returns the network specific directory containing wallets and certificates.
+ */
+ public static File getDataDir() {
+ return getNetworkDir(getDataHome());
+ }
+
+ /**
+ * Returns the network specific directory containing regenerable files.
+ */
+ public static File getCacheDir() {
+ return getNetworkDir(getCacheHome());
+ }
+
+ /**
+ * Returns the network specific directory containing logs and runtime state.
+ */
+ public static File getStateDir() {
+ return getNetworkDir(getStateHome());
+ }
+
+ public static File getConfigHome() {
+ return ApplicationDir.CONFIG.get(SparrowWallet.APP_NAME);
+ }
+
+ public static File getDataHome() {
+ return ApplicationDir.DATA.get(SparrowWallet.APP_NAME);
+ }
+
+ public static File getCacheHome() {
+ return ApplicationDir.CACHE.get(SparrowWallet.APP_NAME);
+ }
+
+ public static File getStateHome() {
+ return getStateHome(false);
+ }
+
+ public static File getStateHome(boolean useDefault) {
+ return ApplicationDir.STATE.get(SparrowWallet.APP_NAME, useDefault);
+ }
+
+ /**
+ * Returns the single directory used when the XDG Base Directory Specification is not followed, ignoring any configured home.
+ *
+ * Provides a fixed location that does not move as categories are migrated, and is where earlier versions wrote all of their files.
+ */
+ public static File getDefaultHome() {
+ return ApplicationDir.getDefaultDir(SparrowWallet.APP_NAME);
+ }
+
+ /**
+ * Resolves the network specific directory within the given application directory, creating it if necessary.
+ *
+ * Where a network has been renamed, any existing directory under the previous name is moved and replaced with a symlink,
+ * and a symlink under the previous name is otherwise maintained for convenience.
+ */
+ private static File getNetworkDir(File applicationDir) {
+ File networkDir;
Network network = Network.get();
if(network != Network.MAINNET) {
- sparrowDir = new File(getSparrowHome(), network.getHome());
- if(!network.getName().equals(network.getHome()) && !sparrowDir.exists()) {
- File networkNameDir = new File(getSparrowHome(), network.getName());
+ networkDir = new File(applicationDir, network.getHome());
+ if(!network.getName().equals(network.getHome()) && !networkDir.exists()) {
+ File networkNameDir = new File(applicationDir, network.getName());
if(networkNameDir.exists() && networkNameDir.isDirectory() && !Files.isSymbolicLink(networkNameDir.toPath())) {
try {
- if(networkNameDir.renameTo(sparrowDir) && !isWindows()) {
- Files.createSymbolicLink(networkNameDir.toPath(), Path.of(sparrowDir.getName()));
+ if(networkNameDir.renameTo(networkDir) && !isWindows()) {
+ Files.createSymbolicLink(networkNameDir.toPath(), Path.of(networkDir.getName()));
}
} catch(Exception e) {
- log.debug("Error creating symlink from " + networkNameDir.getAbsolutePath() + " to " + sparrowDir.getName(), e);
+ log.debug("Error creating symlink from " + networkNameDir.getAbsolutePath() + " to " + networkDir.getName(), e);
}
}
}
} else {
- sparrowDir = getSparrowHome();
+ networkDir = applicationDir;
}
- if(!sparrowDir.exists()) {
- createOwnerOnlyDirectory(sparrowDir);
+ if(!networkDir.exists()) {
+ createOwnerOnlyDirectory(networkDir);
}
if(!network.getName().equals(network.getHome()) && !isWindows()) {
try {
- Path networkNamePath = getSparrowHome().toPath().resolve(network.getName());
+ Path networkNamePath = applicationDir.toPath().resolve(network.getName());
if(Files.isSymbolicLink(networkNamePath)) {
- Path symlinkTarget = getSparrowHome().toPath().resolve(Files.readSymbolicLink(networkNamePath));
- if(!Files.isSameFile(sparrowDir.toPath(), symlinkTarget)) {
+ Path symlinkTarget = applicationDir.toPath().resolve(Files.readSymbolicLink(networkNamePath));
+ if(!Files.isSameFile(networkDir.toPath(), symlinkTarget)) {
Files.delete(networkNamePath);
- Files.createSymbolicLink(networkNamePath, Path.of(sparrowDir.getName()));
+ Files.createSymbolicLink(networkNamePath, Path.of(networkDir.getName()));
}
} else if(!Files.exists(networkNamePath)) {
- Files.createSymbolicLink(networkNamePath, Path.of(sparrowDir.getName()));
+ Files.createSymbolicLink(networkNamePath, Path.of(networkDir.getName()));
}
} catch(Exception e) {
- log.debug("Error updating symlink from " + network.getName() + " to " + sparrowDir.getName(), e);
+ log.debug("Error updating symlink from " + network.getName() + " to " + networkDir.getName(), e);
}
}
- return sparrowDir;
- }
-
- public static File getSparrowHome() {
- return getSparrowHome(false);
- }
-
- public static File getSparrowHome(boolean useDefault) {
- if(!useDefault && System.getProperty(SparrowWallet.APP_HOME_PROPERTY) != null) {
- return new File(System.getProperty(SparrowWallet.APP_HOME_PROPERTY));
- }
-
- if(isWindows()) {
- return new File(getHomeDir(), WINDOWS_SPARROW_DIR);
- }
-
- return new File(getHomeDir(), SPARROW_DIR);
+ return networkDir;
}
- static File getHomeDir() {
- if(isWindows()) {
- return new File(System.getenv("APPDATA"));
+ /**
+ * Logs the application directories in use where they do not all resolve to the default application directory.
+ */
+ public static void logApplicationDirs() {
+ List<ApplicationDir> xdgDirs = Arrays.stream(ApplicationDir.values()).filter(applicationDir -> applicationDir.isXdg(SparrowWallet.APP_NAME)).toList();
+ if(!xdgDirs.isEmpty() && log.isInfoEnabled()) {
+ log.info("Using XDG base directories for " + xdgDirs.stream().map(applicationDir -> applicationDir.toString().toLowerCase(Locale.ROOT)).collect(Collectors.joining(", ")) +
+ " (config: " + getConfigHome() + ", data: " + getDataHome() + ", cache: " + getCacheHome() + ", state: " + getStateHome() + ")");
}
-
- return new File(System.getProperty("user.home"));
}
public static boolean createOwnerOnlyDirectory(File directory) {
diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Tor.java b/src/main/java/com/sparrowwallet/sparrow/net/Tor.java
index 4d4ec7b..94a2898 100644
--- a/src/main/java/com/sparrowwallet/sparrow/net/Tor.java
+++ b/src/main/java/com/sparrowwallet/sparrow/net/Tor.java
@@ -40,15 +40,16 @@ public class Tor implements Closeable {
private IPSocketAddress socksAddress;
public Tor(OnEvent<TorListeners> listener) {
- Path path = Path.of(Storage.getSparrowHome().getAbsolutePath()).resolve(TOR_DIR);
- File oldInstallDir = path.resolve(WORK_DIR).resolve(OLD_INSTALL_DIR).toFile();
+ Path workPath = Path.of(Storage.getStateHome().getAbsolutePath()).resolve(TOR_DIR).resolve(WORK_DIR);
+ Path cachePath = Path.of(Storage.getCacheHome().getAbsolutePath()).resolve(TOR_DIR).resolve(CACHE_DIR);
+ File oldInstallDir = workPath.resolve(OLD_INSTALL_DIR).toFile();
if(oldInstallDir.exists()) {
IOUtils.deleteDirectory(oldInstallDir);
}
TorRuntime.Environment env = TorRuntime.Environment.Builder(
- path.resolve(WORK_DIR).toFile(),
- path.resolve(CACHE_DIR).toFile(),
+ workPath.toFile(),
+ cachePath.toFile(),
ResourceLoaderTorExec::getOrCreate
);
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.