Skip to content

Commit

Permalink
Add option: Use separate folder for .debug files
Browse files Browse the repository at this point in the history
  • Loading branch information
Argent77 committed Oct 23, 2023
1 parent 09aefa9 commit d919af7
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/main/java/io/infinitytools/wit/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ public enum Key {
* Specifies the directory to the last used mod's tp2 file.
*/
LAST_MOD_PATH("Last Mod Path", String.class, null),
/**
* Specifies whether the mod installation log should be saved in a separate folder.
*/
DEBUG_FOLDER_ENABLED("Enable Debug Folder", Boolean.class, false),
/**
* Specifies the folder name where mod installation logs should be saved.
* Folder name is relative to the game directory.
*/
DEBUG_FOLDER_NAME("Debug Folder Name", String.class, "debug"),
/**
* Specifies the language in which all text of the application should be displayed.
* System language is used if this option is not defined.
Expand Down
152 changes: 151 additions & 1 deletion src/main/java/io/infinitytools/wit/gui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,40 @@ private void setTrayIconFeedbackEnabled(boolean newValue) {
}
}

/**
* Returns whether debug files from guided mod installations should be stored in a separate folder.
*/
public boolean isDebugFolderEnabled() {
return getController().debugFolderCheckBox.isSelected();
}

/**
* Defines whether debug files from guided mod installations should be stored in a separate folder.
*/
public void setDebugFolderEnabled(boolean newValue) {
getController().debugFolderCheckBox.setSelected(newValue);
}

/**
* Returns the folder name for debug files from guided mod installations.
*/
public String getDebugFolderName() {
return getController().debugFolderTextField.getText();
}

/**
* Specifies the folder name for debug files from guided mod installations.
*
* @param folderName Folder name, relative to the game directory.
*/
public void setDebugFolderName(String folderName) {
String name = Utils.getValidatedFolderPath(folderName, true);
getController().debugFolderTextField.setText(name);
if (name.isEmpty()) {
getController().debugFolderCheckBox.setSelected(false);
}
}

/**
* Returns whether the Details window is visible.
*/
Expand Down Expand Up @@ -828,6 +862,69 @@ private void onDetailsButtonSelected(boolean newValue) {
}
}

/**
* Ensures that the debug folder input field does not contain invalid characters.
*/
private void onDebugFolderTextChanged() {
final String text = getController().debugFolderTextField.getText();
if (text.isEmpty()) {
return;
}

int caret = getController().debugFolderTextField.getCaretPosition();
StringBuilder sb = new StringBuilder(text);

final String forbiddenChars = "<>:|?* \"\t\n\r\f";
int i = 0;
while (i < sb.length()) {
final char ch = sb.charAt(i);
if (forbiddenChars.indexOf(ch) >= 0) {
sb.delete(i, i + 1);
if (i < caret) {
caret--;
}
} else {
// Unix uses forward slashes for path separators
if (!SystemInfo.IS_WINDOWS && ch == '\\') {
sb.replace(i, i+1, "/");
}
i++;
}
}
caret = Math.min(caret, sb.length());

getController().debugFolderTextField.positionCaret(caret);
getController().debugFolderTextField.setText(sb.toString());

// updating info message
updateDebugFolderMessage();
}

/**
* Called whenever the selected state of the "Debug Folder" option is changed.
*/
private void onDebugFolderSelectionChanged() {
final boolean selected = getController().debugFolderCheckBox.isSelected();
getController().debugFolderTextField.setDisable(!selected);

// updating info message
updateDebugFolderMessage();
}

/**
* Shows or hides an info text associated with the "Debug Folder" option depending on the current option state.
*/
private void updateDebugFolderMessage() {
final boolean defEnabled = Configuration.getInstance().getOption(Configuration.Key.DEBUG_FOLDER_ENABLED);
final boolean curEnabled = getController().debugFolderCheckBox.isSelected();
final String defText = Configuration.getInstance().getOption(Configuration.Key.DEBUG_FOLDER_NAME);
final String curText = getController().debugFolderTextField.getText();
final boolean msgEnabled = defEnabled != curEnabled || !defText.contentEquals(curText);

getController().debugFolderMessageLabel.setVisible(msgEnabled);
getController().debugFolderMessageLabel.setManaged(msgEnabled);
}

/**
* Called by process event handler when a process starts or terminates.
*/
Expand Down Expand Up @@ -1316,6 +1413,12 @@ private void setupUI(Scene scene) {
getController().bufferSizeSlider.valueProperty().addListener((ob, ov, nv) -> setOutputBufferSizeLabel(nv.doubleValue()));
getController().outputFontSizeValueFactory.valueProperty().addListener((ob, ov, nv) -> setOutputAreaFontSize(nv));

getController().debugFolderCheckBox.selectedProperty().addListener((ob, ov, nv) -> onDebugFolderSelectionChanged());
getController().debugFolderTextField.textProperty().addListener((ob, ov, nv) -> onDebugFolderTextChanged());
getController().debugFolderTextField.focusedProperty().addListener((ob, ov, nv) ->
getController().debugFolderTextField
.setText(Utils.getValidatedFolderPath(getController().debugFolderTextField.getText(), true)));

getController().showLogCheckItem.setOnAction(event -> {
final boolean selected = getController().showLogCheckItem.isSelected();
try {
Expand Down Expand Up @@ -1393,6 +1496,10 @@ private void setupUI(Scene scene) {
setTrayIconFeedbackEnabled(Configuration.getInstance().getOption(Configuration.Key.TRAY_ICON_FEEDBACK));
setOutputBufferSize(Configuration.getInstance().getOption(Configuration.Key.BUFFER_LIMIT));

setDebugFolderEnabled(Configuration.getInstance().getOption(Configuration.Key.DEBUG_FOLDER_ENABLED));
setDebugFolderName(Configuration.getInstance().getOption(Configuration.Key.DEBUG_FOLDER_NAME));
onDebugFolderSelectionChanged();

applyOutputFontSize(Configuration.getInstance().getOption(Configuration.Key.OUTPUT_FONT_SIZE,
getController().outputArea.getFont().getSize()));

Expand Down Expand Up @@ -1765,6 +1872,8 @@ private void updateConfiguration() {
cfg.setOption(Configuration.Key.TRAY_ICON_FEEDBACK, isTrayIconFeedbackEnabled());
cfg.setOption(Configuration.Key.QUIT_ON_ENTER, isAutoQuitEnabled());
cfg.setOption(Configuration.Key.VISUALIZE_RESULT, isVisualizeResultsEnabled());
cfg.setOption(Configuration.Key.DEBUG_FOLDER_ENABLED, isDebugFolderEnabled());
cfg.setOption(Configuration.Key.DEBUG_FOLDER_NAME, getDebugFolderName());
cfg.setOption(Configuration.Key.BUFFER_LIMIT, getOutputBufferSize());
cfg.setOption(Configuration.Key.OUTPUT_FONT_SIZE, getOutputAreaFontSize());
if (modInfo != null) {
Expand Down Expand Up @@ -2121,7 +2230,7 @@ private String[] getWeiduCommand(String gameLang, Path tp2File) {
final List<String> command = new ArrayList<>(Configuration.getInstance().getWeiduArgs());

if (!command.contains("--log")) {
final Path logFile = getModInfo().getLogFile();
final Path logFile = getEffectiveLogFilePath(true);
command.add(0, logFile.toString());
command.add(0, "--log");
}
Expand All @@ -2148,6 +2257,47 @@ private String[] getWeiduCommand(String gameLang, Path tp2File) {
return command.toArray(new String[0]);
}

/**
* Returns the effective path of the .debug file created by the mod installation process, which also considers
* the option to redirect debug files to custom folders.
*
* @param autoCreateFolders Specify {@code true} to ensure that the folder for the log file exists.
* @return Effective log file path.
*/
public Path getEffectiveLogFilePath(boolean autoCreateFolders) {
Path logFile = getModInfo().getLogFile();

// relocate log path if needed
if (isDebugFolderEnabled()) {
try {
final Path subPath = Path.of(getDebugFolderName());
if (subPath.getNameCount() > 0) {
Path path;
if (logFile.getParent() == null) {
path = subPath;
} else {
path = logFile.getParent().resolve(subPath);
}
path = path.resolve(logFile.getFileName());
logFile = path;
}
} catch (Exception ignored) {
}
}

if (autoCreateFolders && logFile.getParent() != null && !Files.isDirectory(logFile.getParent())) {
try {
Files.createDirectories(logFile.getParent());
} catch (IOException e) {
// falling back to original path on error
Logger.warn(e, "Could not create folder for debug file");
logFile = getModInfo().getLogFile();
}
}

return logFile;
}

/**
* Handles files dropped by a Drag&Drop operation or similar action.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public class MainWindowController implements Initializable {
public CheckMenuItem trayIconFeedbackCheckItem;
public CheckMenuItem showLogCheckItem;
public CustomMenuItem bufferSizeMenuItem;
public CustomMenuItem debugFolderMenuItem;
public CheckBox debugFolderCheckBox;
public TextField debugFolderTextField;
public Label debugFolderMessageLabel;
public CustomMenuItem outputFontSizeMenuItem;
public Spinner<Double> outputFontSizeSpinner;
public SpinnerValueFactory.DoubleSpinnerValueFactory outputFontSizeValueFactory;
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/io/infinitytools/wit/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
Expand Down Expand Up @@ -294,6 +295,56 @@ public static String normalizePath(String path) {
return retVal;
}

/**
* Ensures that the specified folder name conforms to the file name specification.
*
* @param pathName Folder path as string.
* @param relative Indicates whether only relative folder paths are allowed.
* @return A valid path name conforming to the specified parameters.
*/
public static String getValidatedFolderPath(String pathName, boolean relative) {
if (pathName == null) {
pathName = "";
}

try {
Path path = Path.of(pathName);
if (relative && path.getNameCount() > 0 && path.isAbsolute()) {
path = path.subpath(1, path.getNameCount());
}

// removing or fixing invalid path names
List<String> pathNames = new ArrayList<>();
for (Path value : path) {
String item = value.toString().strip();

boolean check = !item.isEmpty() && !item.equals("..") && !item.equals(".");
if (check) {
while (item.endsWith(".")) {
item = item.substring(0, item.length() - 1);
}
}

if (!item.isEmpty() && !item.equals("..") && !item.equals(".")) {
pathNames.add(item);
}
}

// assembling final path
if (pathNames.isEmpty()) {
pathName = "";
} else {
final String firstName = pathNames.removeFirst();
pathName = Path.of(firstName, pathNames.toArray(new String[0])).toString();
}
} catch (InvalidPathException e) {
Logger.debug("Invalid folder path: {}", pathName);
pathName = "";
}

return pathName;
}

/**
* Attempts to resolve the specified path to an existing file or directory by looking at the parent path at each
* unsuccessful pass.
Expand Down
25 changes: 25 additions & 0 deletions src/main/resources/io/infinitytools/wit/gui/main.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,31 @@

<SeparatorMenuItem />

<CustomMenuItem fx:id="debugFolderMenuItem">
<content>
<VBox alignment="CENTER_LEFT"
spacing="8">
<padding>
<Insets top="2"
right="4"
bottom="2"/>
</padding>
<CheckBox fx:id="debugFolderCheckBox"
text="%ui.main.menu.item.useDebugFolder"/>
<TextField fx:id="debugFolderTextField"
text="debug" />
<!-- managed=false signals the layout manager to not reserve space for the invisible node -->
<Label fx:id="debugFolderMessageLabel"
styleClass="warningLabel"
text="%ui.main.menu.item.useDebugFolder.label"
visible="false"
managed="false"/>
</VBox>
</content>
</CustomMenuItem>

<SeparatorMenuItem />

<CustomMenuItem fx:id="outputFontSizeMenuItem">
<content>
<HBox alignment="CENTER_LEFT"
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/io/infinitytools/wit/gui/mainStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@
.boldLabel {
-fx-font-weight: bold;
}

.warningLabel {
-fx-text-fill: red;
}
2 changes: 2 additions & 0 deletions src/main/resources/l10n/wit.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ ui.main.menu.item.singleInstance = Run Application in Single Instance Mode
ui.main.menu.item.singleInstance.message.content = Please restart the application for the option to take effect.
ui.main.menu.item.trayIconFeedback = Show Status in Tray Icon
ui.main.menu.item.logWindow = Show Log Window
ui.main.menu.item.useDebugFolder = Use Separate Folder for .debug Files:
ui.main.menu.item.useDebugFolder.label = Restart required to apply the changes.
ui.main.menu.item.outputFontSize.prompt.label = Output Font Size:
ui.main.menu.item.outputFontSize.pt.label = pt
ui.main.menu.item.bufferSize.label = Output Buffer Size:
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/l10n/wit_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ ui.main.menu.item.singleInstance = Nur eine Anwendungsinstanz erlauben
ui.main.menu.item.singleInstance.message.content = Die Anwendung muss neu gestartet werden, um die Option zu aktivieren.
ui.main.menu.item.trayIconFeedback = Zustand im Taskleistensymbol anzeigen
ui.main.menu.item.logWindow = Protokollfenster anzeigen
ui.main.menu.item.useDebugFolder = Eigener Ordner für .debug-Dateien:
ui.main.menu.item.useDebugFolder.label = Neustart erforderlich, um die Änderungen zu übernehmen.
ui.main.menu.item.outputFontSize.prompt.label = Schriftgröße der Ausgabe:
ui.main.menu.item.outputFontSize.pt.label = pt
ui.main.menu.item.bufferSize.label = Puffergröße der Ausgabe:
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/l10n/wit_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ ui.main.menu.item.singleInstance = Lancer l'application en mode Instance Unique
ui.main.menu.item.singleInstance.message.content = Veuillez redémarrer l'application pour que l'option soit fonctionnelle.
ui.main.menu.item.trayIconFeedback = Montrer l'état d’avancement dans l’icône de la barre des tâches
ui.main.menu.item.logWindow = Afficher la fenêtre du journal
# TODO: translate
ui.main.menu.item.useDebugFolder = Use Separate Folder for .debug Files:
ui.main.menu.item.useDebugFolder.label = Restart required to apply the changes.
ui.main.menu.item.outputFontSize.prompt.label = Dimension du Journal d'installation :
ui.main.menu.item.outputFontSize.pt.label = pt
ui.main.menu.item.bufferSize.label = Mémoire tampon allouée au Journal d'installation :
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/l10n/wit_pt_BR.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ ui.main.menu.item.singleInstance = Executar o Aplicativo no Modo de Instância
ui.main.menu.item.singleInstance.message.content = Por favor reinicie o aplicativo pra opção ter efeito.
ui.main.menu.item.trayIconFeedback = Mostrar o Status no Ícone do Tray
ui.main.menu.item.logWindow = Mostrar a Janela do Registro
# TODO: translate
ui.main.menu.item.useDebugFolder = Use Separate Folder for .debug Files:
ui.main.menu.item.useDebugFolder.label = Restart required to apply the changes.
ui.main.menu.item.outputFontSize.prompt.label = Tamanho da Fonte de Saída:
ui.main.menu.item.outputFontSize.pt.label = pt
ui.main.menu.item.bufferSize.label = Tamanho do Buffer de Saída:
Expand Down

0 comments on commit d919af7

Please sign in to comment.