Terasology/engine/src/main/java/org/terasology/engine/paths/PathManager.java

365 lines
13 KiB
Java

// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.engine.paths;
import com.google.common.collect.ImmutableList;
import com.sun.jna.platform.win32.KnownFolders;
import com.sun.jna.platform.win32.Shell32Util;
import org.terasology.context.Context;
import org.terasology.engine.subsystem.DisplayDevice;
import org.terasology.utilities.OS;
import javax.swing.JFileChooser;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* Manager class that keeps track of the game's various paths and save directories.
*/
public final class PathManager {
private static final String TERASOLOGY_FOLDER_NAME = "Terasology";
private static final Path LINUX_HOME_SUBPATH = Paths.get(".local", "share", "terasology");
private static final String SAVED_GAMES_DIR = "saves";
private static final String RECORDINGS_LIBRARY_DIR = "recordings";
private static final String LOG_DIR = "logs";
private static final String SHADER_LOG_DIR = "shaders";
private static final String MODULE_DIR = "modules";
private static final String MODULE_CACHE_DIR = "cachedModules";
private static final String SCREENSHOT_DIR = "screenshots";
private static final String NATIVES_DIR = "natives";
private static final String CONFIGS_DIR = "configs";
private static final String SANDBOX_DIR = "sandbox";
private static final String REGEX = "[^A-Za-z0-9-_ ]";
private static PathManager instance;
private static Context context;
private Path installPath;
private Path homePath;
private Path savesPath;
private Path recordingsPath;
private Path logPath;
private Path shaderLogPath;
private Path currentWorldPath;
private Path sandboxPath;
private ImmutableList<Path> modPaths = ImmutableList.of();
private Path screenshotPath;
private Path nativesPath;
private Path configsPath;
private PathManager() {
// By default, the path should be the code location (where terasology.jar is)
try {
URL urlToSource = PathManager.class.getProtectionDomain().getCodeSource().getLocation();
// TODO: Probably remove this after a while, confirming via logs that this is no longer needed
Path codeLocation = Paths.get(urlToSource.toURI());
System.out.println("PathManager being initialized. Initial code location is " + codeLocation.toAbsolutePath());
// Theory that whatever reason we needed to get the path that way isn't needed anymore - try just current dir ...
codeLocation = Paths.get("").toAbsolutePath();
System.out.println("Switched it to expected working dir: " + codeLocation.toAbsolutePath());
// If that fails in some situations a different approach could test for the following in the path:
// if (codeLocation.toString().contains(".gradle") || codeLocation.toString().contains(".m2")) {
if (Files.isRegularFile(codeLocation)) {
installPath = findNativesHome(codeLocation.getParent(), 5);
if (installPath == null) {
System.out.println("Failed to find the natives dir - unable to launch!");
throw new RuntimeException("Failed to find natives from .jar launch");
}
}
} catch (URISyntaxException e) {
// Can't use logger, because logger not set up when PathManager is used.
System.out.println("Failed to convert code location to uri");
}
// We might be running from an IDE which can cause the installPath to be null. Try current working directory.
if (installPath == null) {
installPath = Paths.get("").toAbsolutePath();
System.out.println("installPath was null, running from IDE or headless server? Setting to: " + installPath);
installPath = findNativesHome(installPath, 5);
if (installPath == null) {
System.out.println("Failed to find the natives dir - unable to launch!");
throw new RuntimeException("Failed to find natives from likely IDE launch");
}
}
homePath = installPath;
}
/**
* Searches for a parent directory containing the natives directory
*
* @param startPath path to start from
* @param maxDepth max directory levels to search
* @return the adjusted path containing the natives directory or null if not found
*/
private Path findNativesHome(Path startPath, int maxDepth) {
int levelsToSearch = maxDepth;
Path checkedPath = startPath;
while (levelsToSearch > 0) {
File dirToTest = new File(checkedPath.toFile(), NATIVES_DIR);
if (dirToTest.exists()) {
System.out.println("Found the natives dir: " + dirToTest);
return checkedPath;
}
checkedPath = checkedPath.getParent();
if (checkedPath.equals(startPath.getRoot())) {
System.out.println("Uh oh, reached the root path, giving up");
return null;
}
levelsToSearch--;
}
System.out.println("Failed to find the natives dir within " + maxDepth + " levels of " + startPath);
return null;
}
/**
*
* @return An instance of the path manager for this execution.
*/
public static PathManager getInstance() {
if (instance == null) {
instance = new PathManager();
}
return instance;
}
/**
* Uses the given path as the home instead of the default home path.
* @param rootPath Path to use as the home path.
* @throws IOException Thrown when required directories cannot be accessed.
*/
public void useOverrideHomePath(Path rootPath) throws IOException {
this.homePath = rootPath;
updateDirs();
}
/**
* Uses a platform-specific default home path for this execution.
* @throws IOException Thrown when required directories cannot be accessed.
*/
public void useDefaultHomePath() throws IOException {
switch (OS.get()) {
case LINUX:
homePath = Paths.get(System.getProperty("user.home")).resolve(LINUX_HOME_SUBPATH);
break;
case MACOSX:
homePath = Paths.get(System.getProperty("user.home"), "Library", "Application Support", TERASOLOGY_FOLDER_NAME);
break;
case WINDOWS:
String savedGamesPath = Shell32Util
.getKnownFolderPath(KnownFolders.FOLDERID_SavedGames);
if (savedGamesPath == null) {
savedGamesPath = Shell32Util
.getKnownFolderPath(KnownFolders.FOLDERID_Documents);
}
Path rawPath;
if (savedGamesPath != null) {
rawPath = Paths.get(savedGamesPath);
} else {
rawPath = new JFileChooser().getFileSystemView().getDefaultDirectory()
.toPath();
}
homePath = rawPath.resolve(TERASOLOGY_FOLDER_NAME);
break;
default:
homePath = Paths.get(System.getProperty("user.home")).resolve(LINUX_HOME_SUBPATH);
break;
}
updateDirs();
}
/**
* Gives user the option to manually choose home path.
* @throws IOException Thrown when required directories cannot be accessed.
*/
public void chooseHomePathManually() throws IOException {
DisplayDevice display = context.get(DisplayDevice.class);
boolean isHeadless = display.isHeadless();
if (!isHeadless) {
Path rawPath = new JFileChooser().getFileSystemView().getDefaultDirectory()
.toPath();
homePath = rawPath.resolve("Terasology");
} else {
// If the system is headless
homePath = Paths.get("").toAbsolutePath();
}
updateDirs();
}
/**
*
* @return This execution's home path.
*/
public Path getHomePath() {
return homePath;
}
/**
*
* @return The path of the running installation.
*/
public Path getInstallPath() {
return installPath;
}
/**
*
* @return Path in which world saves are saved.
*/
public Path getSavesPath() {
return savesPath;
}
/**
*
* @return Path in which recordings are saved.
*/
public Path getRecordingsPath() {
return recordingsPath;
}
/**
*
* @return Path in which this execution's logs are saved.
*/
public Path getLogPath() {
return logPath;
}
/**
*
* @return Path in which this execution's shader logs are saved.
*/
public Path getShaderLogPath() {
return shaderLogPath;
}
/**
*
* @return List of paths to all of the modules.
*/
public List<Path> getModulePaths() {
return modPaths;
}
/**
*
* @return Path in which this execution's screen-shots are saved.
*/
public Path getScreenshotPath() {
return screenshotPath;
}
/**
*
* @return Path in which the game's native libraries are saved.
*/
public Path getNativesPath() {
return nativesPath;
}
/**
*
* @return Path in which the game's config files are saved.
*/
public Path getConfigsPath() {
return configsPath;
}
/**
*
* @return Path in which the modules are allowed to save files.
*/
public Path getSandboxPath() {
return sandboxPath;
}
/**
* Updates all of the path manager's file/directory references to match the path settings. Creates directories if they don't already exist.
* @throws IOException Thrown when required directories cannot be accessed.
*/
private void updateDirs() throws IOException {
Files.createDirectories(homePath);
savesPath = homePath.resolve(SAVED_GAMES_DIR);
Files.createDirectories(savesPath);
recordingsPath = homePath.resolve(RECORDINGS_LIBRARY_DIR);
Files.createDirectories(recordingsPath);
logPath = homePath.resolve(LOG_DIR);
Files.createDirectories(logPath);
shaderLogPath = logPath.resolve(SHADER_LOG_DIR);
Files.createDirectories(shaderLogPath);
Path homeModPath = homePath.resolve(MODULE_DIR);
Files.createDirectories(homeModPath);
Path modCachePath = homePath.resolve(MODULE_CACHE_DIR);
Files.createDirectories(modCachePath);
if (Files.isSameFile(homePath, installPath)) {
modPaths = ImmutableList.of(modCachePath, homeModPath);
} else {
Path installModPath = installPath.resolve(MODULE_DIR);
Files.createDirectories(installModPath);
modPaths = ImmutableList.of(installModPath, modCachePath, homeModPath);
}
screenshotPath = homePath.resolve(SCREENSHOT_DIR);
Files.createDirectories(screenshotPath);
nativesPath = installPath.resolve(NATIVES_DIR);
configsPath = homePath.resolve(CONFIGS_DIR);
if (currentWorldPath == null) {
currentWorldPath = homePath;
}
sandboxPath = homePath.resolve(SANDBOX_DIR);
Files.createDirectories(sandboxPath);
// --------------------------------- Setup native paths ---------------------
final Path path;
switch (OS.get()) {
case WINDOWS:
path = PathManager.getInstance().getNativesPath().resolve("windows");
break;
case MACOSX:
path = PathManager.getInstance().getNativesPath().resolve("macosx");
break;
case LINUX:
path = PathManager.getInstance().getNativesPath().resolve("linux");
break;
default:
throw new UnsupportedOperationException("Unsupported operating system: " + System.getProperty("os" +
".name"));
}
final String natives = path.toAbsolutePath().toString();
System.setProperty("org.lwjgl.librarypath", natives);
System.setProperty("net.java.games.input.librarypath", natives); // libjinput
System.setProperty("org.terasology.librarypath", natives); // JNBullet
}
public Path getHomeModPath() {
return modPaths.get(0);
}
public Path getSavePath(String title) {
return getSavesPath().resolve(title.replaceAll(REGEX, ""));
}
public Path getRecordingPath(String title) {
return getRecordingsPath().resolve(title.replaceAll(REGEX, ""));
}
public Path getSandboxPath(String title) {
return getSandboxPath().resolve(title.replaceAll(REGEX, ""));
}
}