Skip to content

Commit

Permalink
chore: introduce Installation<T> interface (#710)
Browse files Browse the repository at this point in the history
* chore: rename game.Installation >>> game.GameInstallation
* chore: add `Installation<T>` interface (intended for both JRE and game installations)
* chore: make `GameInstallation` implement `Installation<GameIdentifier>``
* chore: remove (deprecated) `GameManager::getInstalledVersion`
* fix: don't try to read verison info from JAR in GameInstallation::getInfo
* qa: fix checkstyle issues

Co-authored-by: jdrueckert <[email protected]>
  • Loading branch information
skaldarnar and jdrueckert authored Nov 29, 2023
1 parent 223ef54 commit 79c8a99
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

import com.google.common.base.MoreObjects;
import org.semver4j.Semver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.launcher.io.Installation;
import org.terasology.launcher.model.Build;
import org.terasology.launcher.model.GameIdentifier;
import org.terasology.launcher.model.Profile;

import java.io.FileNotFoundException;
import java.io.IOException;
Expand All @@ -24,21 +30,23 @@
/**
* A local installation of a Terasology release.
*/
public class Installation {
final Path path;
public class GameInstallation implements Installation<GameIdentifier> {

Installation(Path installDirectory) {
private static final Logger logger = LoggerFactory.getLogger(GameInstallation.class);
private final Path path;

GameInstallation(Path installDirectory) {
path = checkNotNull(installDirectory);
}

/**
* Return an Installation after confirming it is present.
*/
static Installation getExisting(Path directory) throws FileNotFoundException {
static GameInstallation getExisting(Path directory) throws FileNotFoundException {
if (!Files.exists(directory)) {
throw new FileNotFoundException("No installation present in " + directory);
}
return new Installation(directory);
return new GameInstallation(directory);
}

/**
Expand All @@ -53,25 +61,25 @@ Semver getEngineVersion() throws IOException {

/**
* Locate the main game jar.
*
* <p>
* As of August 2021, Terasology has custom build logic to put libraries into a {@code libs} (plural) subdirectory.
* As we plan to switch to using default Gradle behavior we have to do a quick check how the game distribution was
* build (i.e., custom {@code libs} or default {@code lib}).
*/
Path getGameJarPath() throws IOException {
return findJar(path, Installation::matchGameJar, "game");
return findJar(path, GameInstallation::matchGameJar, "game");
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Installation)) {
if (!(o instanceof GameInstallation)) {
return false;
}

Installation that = (Installation) o;
GameInstallation that = (GameInstallation) o;

return path.equals(that.path);
}
Expand Down Expand Up @@ -115,7 +123,7 @@ static Path findJar(Path searchPath, BiPredicate<Path, BasicFileAttributes> pred
* @throws FileNotFoundException if the engine or the version info could not be found
*/
static Semver getEngineVersion(Path versionDirectory) throws IOException {
Path engineJar = findJar(versionDirectory, Installation::matchEngineJar, "engine");
Path engineJar = findJar(versionDirectory, GameInstallation::matchEngineJar, "engine");
Properties versionInfo = getVersionPropertiesFromJar(engineJar);
return new Semver(versionInfo.getProperty("engineVersion"));
}
Expand Down Expand Up @@ -189,4 +197,36 @@ static Properties getVersionPropertiesFromJar(Path jarLocation) throws IOExcepti
return properties;
}
}

@Override
public Path getPath() {
return path;
}

@Override
public GameIdentifier getInfo() {
//TODO: compute this information on instance creation (and fail creation in case it is not a valid installation)
Profile profile;
Build build;
var parts = path.getNameCount();
try {
profile = Profile.valueOf(path.getName(parts - 3).toString());
build = Build.valueOf(path.getName(parts - 2).toString());
return new GameIdentifier(path.getFileName().toString(), build, profile);
} catch (IllegalArgumentException e) {
logger.debug("Cannot derive game information from installation: "
+ "Expected directory format '.../<profile>/<build>/<game>' but got {}", path);
return null;
}
//TODO: this is not working as I expected - probably don't fully understand this code I copied from somewhere else...
// try {
// Path jarPath = getGameJarPath();
// Properties info = getVersionPropertiesFromJar(jarPath);
// return new GameIdentifier(info.getProperty("displayVersion"), build, profile);
// } catch (IOException e) {
// logger.debug("Cannot derive game information from installation: "
// + "Cannot read 'displayVersion' from version info file for installation in {}.", path, e);
// return null;
// }
}
}
23 changes: 4 additions & 19 deletions src/main/java/org/terasology/launcher/game/GameManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.launcher.model.Build;
import org.terasology.launcher.model.GameIdentifier;
import org.terasology.launcher.model.GameRelease;
import org.terasology.launcher.model.Profile;
import org.terasology.launcher.tasks.ProgressListener;
import org.terasology.launcher.util.DownloadException;
import org.terasology.launcher.util.DownloadUtils;
Expand Down Expand Up @@ -142,8 +140,8 @@ public Path getInstallDirectory(GameIdentifier id) {
return installDirectory.resolve(id.getProfile().name()).resolve(id.getBuild().name()).resolve(id.getDisplayVersion());
}

public Installation getInstallation(GameIdentifier id) throws FileNotFoundException {
return Installation.getExisting(getInstallDirectory(id));
public GameInstallation getInstallation(GameIdentifier id) throws FileNotFoundException {
return GameInstallation.getExisting(getInstallDirectory(id));
}

/**
Expand All @@ -157,7 +155,8 @@ private void scanInstallationDir() {
// Skip the intermediate directories.
.filter(d -> installDirectory.relativize(d).getNameCount() == 3);
localGames = gameDirectories
.map(GameManager::getInstalledVersion)
.map(GameInstallation::new)
.map(GameInstallation::getInfo)
.filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableSet());
} catch (IOException e) {
Expand All @@ -166,18 +165,4 @@ private void scanInstallationDir() {
}
Platform.runLater(() -> installedGames.addAll(localGames));
}

private static GameIdentifier getInstalledVersion(Path versionDirectory) {
Profile profile;
Build build;
var parts = versionDirectory.getNameCount();
try {
profile = Profile.valueOf(versionDirectory.getName(parts - 3).toString());
build = Build.valueOf(versionDirectory.getName(parts - 2).toString());
} catch (IllegalArgumentException e) {
logger.debug("Directory does not match expected profile/build names: {}", versionDirectory, e);
return null;
}
return new GameIdentifier(versionDirectory.getFileName().toString(), build, profile);
}
}
14 changes: 7 additions & 7 deletions src/main/java/org/terasology/launcher/game/GameService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* This service starts and monitors the game process.
* <p>
* Its {@linkplain #GameService() constructor} requires no arguments. Use {@link #start(Installation, Settings)} to
* Its {@linkplain #GameService() constructor} requires no arguments. Use {@link #start(GameInstallation, Settings)} to
* start the game process; the zero-argument form of {@code start()} will not have enough information.
* <p>
* The Boolean value of this service is true when it believes the game process has started <em>successfully.</em>
Expand All @@ -44,7 +44,7 @@
public class GameService extends Service<Boolean> {
private static final Logger logger = LoggerFactory.getLogger(GameService.class);

private Installation gamePath;
private GameInstallation gamePath;
private Settings settings;

public GameService() {
Expand All @@ -59,20 +59,20 @@ public GameService() {

/**
* Start a new game process with these settings.
* @param installation the directory under which we will find libs/Terasology.jar, also used as the process's
* @param gameInstallation the directory under which we will find libs/Terasology.jar, also used as the process's
* working directory
* @param settings supplies other settings relevant to configuring a process
*/
@SuppressWarnings("checkstyle:HiddenField")
public void start(Installation installation, Settings settings) {
this.gamePath = installation;
public void start(GameInstallation gameInstallation, Settings settings) {
this.gamePath = gameInstallation;
this.settings = settings;

start();
}

/**
* Use {@link #start(Installation, Settings)} instead.
* Use {@link #start(GameInstallation, Settings)} instead.
* <p>
* It is an error to call this method before providing the configuration.
*/
Expand Down Expand Up @@ -118,7 +118,7 @@ public void restart() {
* @throws RuntimeException when required files in the game directory are missing or inaccessible
*/
@Override
protected RunGameTask createTask() throws GameVersionNotSupportedException{
protected RunGameTask createTask() throws GameVersionNotSupportedException {
verifyNotNull(settings);

GameStarter starter;
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/terasology/launcher/game/GameStarter.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class GameStarter implements Callable<Process> {
final ProcessBuilder processBuilder;

/**
* @param installation the directory under which we will find {@code libs/Terasology.jar}, also used as the process's
* @param gameInstallation the directory under which we will find {@code libs/Terasology.jar}, also used as the process's
* working directory
* @param gameDataDirectory {@code -homedir}, the directory where Terasology's data files (saves & etc) are kept
* @param heapMin java's {@code -Xms}
Expand All @@ -37,10 +37,10 @@ final class GameStarter implements Callable<Process> {
* @param gameParams additional arguments for the Terasology command line
* @param logLevel the minimum level of log events Terasology will include on its output stream to us
*/
GameStarter(Installation installation, Path gameDataDirectory, JavaHeapSize heapMin, JavaHeapSize heapMax,
GameStarter(GameInstallation gameInstallation, Path gameDataDirectory, JavaHeapSize heapMin, JavaHeapSize heapMax,
List<String> javaParams, List<String> gameParams, Level logLevel) throws IOException, GameVersionNotSupportedException {
Semver engineVersion = installation.getEngineVersion();
var gamePath = installation.path;
Semver engineVersion = gameInstallation.getEngineVersion();
var gamePath = gameInstallation.getPath();

final boolean isMac = Platform.getPlatform().isMac();
final List<String> processParameters = new ArrayList<>();
Expand All @@ -65,7 +65,7 @@ final class GameStarter implements Callable<Process> {
processParameters.addAll(javaParams);

processParameters.add("-jar");
processParameters.add(installation.getGameJarPath().toString());
processParameters.add(gameInstallation.getGameJarPath().toString());

// Parameters after this are for the game facade, not the java runtime.
processParameters.add(homeDirParameter(gameDataDirectory, engineVersion));
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/org/terasology/launcher/io/Installation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2023 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

package org.terasology.launcher.io;

import java.nio.file.Path;

//TODO: define behavior in error cases, annotate non-null, etc.
public interface Installation<T> {

/**
* @return The full path to the location of the installation.
*/
Path getPath();

/**
* @return The information object describing <i>what</i> is installed.
*/
T getInfo();

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import org.terasology.launcher.game.GameManager;
import org.terasology.launcher.game.GameService;
import org.terasology.launcher.game.GameVersionNotSupportedException;
import org.terasology.launcher.game.Installation;
import org.terasology.launcher.game.GameInstallation;
import org.terasology.launcher.model.Build;
import org.terasology.launcher.model.GameIdentifier;
import org.terasology.launcher.model.GameRelease;
Expand Down Expand Up @@ -400,9 +400,9 @@ protected void startGameAction() {
return;
}
final GameRelease release = selectedRelease.getValue();
final Installation installation;
final GameInstallation gameInstallation;
try {
installation = gameManager.getInstallation(release.getId());
gameInstallation = gameManager.getInstallation(release.getId());
} catch (FileNotFoundException e) {
// TODO: Refresh the list of installed games or something? This should not be reachable if
// the properties are up to date.
Expand All @@ -411,7 +411,7 @@ protected void startGameAction() {
return;
}
try {
gameService.start(installation, launcherSettings);
gameService.start(gameInstallation, launcherSettings);
} catch (GameVersionNotSupportedException e) {
Dialogs.showError(stage, e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import java.nio.file.Path;

/** An Installation that does not depend on filesystem interaction to determine engineVersion or game jar. */
class StubInstallation extends Installation {
class StubGameInstallation extends GameInstallation {

Path gameJar;

StubInstallation(Path installDirectory, Path relativeGameJarPath) {
StubGameInstallation(Path installDirectory, Path relativeGameJarPath) {
super(installDirectory);
gameJar = this.path.resolve(relativeGameJarPath);
gameJar = this.getPath().resolve(relativeGameJarPath);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private GameStarter newStarter() throws IOException {
}

private GameStarter newStarter(Path relativeGameJarPath) throws IOException {
return new GameStarter(new StubInstallation(gamePath, relativeGameJarPath),
return new GameStarter(new StubGameInstallation(gamePath, relativeGameJarPath),
gameDataPath, HEAP_MIN, HEAP_MAX, javaParams, gameParams, LOG_LEVEL);
}

Expand Down

0 comments on commit 79c8a99

Please sign in to comment.