diff --git a/CHANGELOG.md b/CHANGELOG.md index 384f702..edc3723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ All notable changes to this project will are documented in this changelog file. ## Unreleased ### Added -- A bit more convenient Hex Editor -- Support for keeping selection when switching between BytesEditor tabs +- A bit more convenient Hex Editor (smart backspace/delete, support undo/redo, support keeping selection between tabs) +- Use working directory for petep.json file - Fixed copy&paste behaviour of Text editor for bytes +- Small refactors and fixes ## [2.1.0] - 2023-05-10 ### Added diff --git a/README.md b/README.md index eab4b50..7871626 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ chmod +x petep.sh petep.bat ``` -***Note:** These run scripts contain useful variables, including path to Java executable. +***Note:** These run scripts contain useful variables, +including working directory (for `petep.json` file), and path to Java executable. You might need to change it if you do not have it in PATH or you use multiple Java versions.* ## Supported functionality diff --git a/scripts/petep.bat b/scripts/petep.bat index 56f17f1..1b147d0 100644 --- a/scripts/petep.bat +++ b/scripts/petep.bat @@ -1,13 +1,18 @@ @echo off + +rem Useful params, change if needed set JAVA_EXE=java.exe -set JAVAW_EXE=javaw.exe -set DIRNAME=%~dp0 -set APP_HOME=%DIRNAME% +set JAVAW_EXE=java.exe +set APP_HOME=%~dp0 +set WORKING_DIR=%APP_HOME% set CMD_LINE_ARGS=%* set DEFAULT_JVM_OPTS= set CLASSPATH=%APP_HOME%\lib\* set MAIN_CLASS="com.warxim.petep.Main" +rem Set working directory (important for petep.json), by default the startup directory is used +cd /D "%WORKING_DIR%" + if "%~2"=="--nogui" ( goto NOGUI ) diff --git a/scripts/petep.sh b/scripts/petep.sh index 1d7fccb..5cbfff9 100644 --- a/scripts/petep.sh +++ b/scripts/petep.sh @@ -1,16 +1,22 @@ #!/bin/bash +# Useful params, change if needed JAVA="java" -APP_HOME="`pwd`" +APP_HOME=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) +WORKING_DIR=$APP_HOME DEFAULT_JVM_OPTS= CMD_LINE_ARGS=$@ CLASSPATH=$APP_HOME/lib/* MAIN_CLASS="com.warxim.petep.Main" LOG_FILE=petep.log +# Set working directory (important for petep.json), by default the startup directory is used +cd $WORKING_DIR + +# Run PETEP if [[ $2 == '--nogui' ]]; then - $JAVA -cp "$CLASSPATH" $MAIN_CLASS $CMD_LINE_ARGS + $JAVA $DEFAULT_JVM_OPTS -cp "$CLASSPATH" $MAIN_CLASS $CMD_LINE_ARGS else - nohup $JAVA -cp "$CLASSPATH" $MAIN_CLASS $CMD_LINE_ARGS > $LOG_FILE & -fi \ No newline at end of file + nohup $JAVA $DEFAULT_JVM_OPTS -cp "$CLASSPATH" $MAIN_CLASS $CMD_LINE_ARGS > $LOG_FILE & +fi diff --git a/src/main/java/com/warxim/petep/Bundle.java b/src/main/java/com/warxim/petep/Bundle.java index 8acc3b1..fa491ee 100644 --- a/src/main/java/com/warxim/petep/Bundle.java +++ b/src/main/java/com/warxim/petep/Bundle.java @@ -19,7 +19,12 @@ import com.warxim.petep.bootstrap.CommandLineArguments; import com.warxim.petep.common.Constant; import com.warxim.petep.common.ContextType; -import com.warxim.petep.configuration.*; +import com.warxim.petep.configuration.ExtensionsLoader; +import com.warxim.petep.configuration.ExtensionsSaver; +import com.warxim.petep.configuration.ModulesLoader; +import com.warxim.petep.configuration.ModulesSaver; +import com.warxim.petep.configuration.ProjectLoader; +import com.warxim.petep.configuration.ProjectSaver; import com.warxim.petep.core.PetepManager; import com.warxim.petep.core.listener.PetepListenerManager; import com.warxim.petep.exception.ConfigurationException; @@ -34,7 +39,7 @@ import com.warxim.petep.util.FileUtils; import lombok.Getter; -import java.io.File; +import java.nio.file.Path; /** * Singleton for PETEP assets. @@ -91,13 +96,15 @@ public void load(CommandLineArguments arguments) throws ConfigurationException { contextType = arguments.getContextType(); projectPath = arguments.getProjectPath(); - var configDirectory = FileUtils.getProjectFileAbsolutePath(Constant.PROJECT_CONFIG_DIRECTORY) + File.separator; + var configDirectory = Path.of(FileUtils.getProjectFileAbsolutePath(Constant.PROJECT_CONFIG_DIRECTORY)); - project = ProjectLoader.load(configDirectory + Constant.PROJECT_CONFIG_FILE); + project = ProjectLoader.load(configDirectory.resolve(Constant.PROJECT_CONFIG_FILE).toString()); receiverManager = new ReceiverManager(); // Create managers. - extensionManager = new ExtensionManager(ExtensionsLoader.load(configDirectory + Constant.EXTENSIONS_CONFIG_FILE)); + extensionManager = new ExtensionManager(ExtensionsLoader.load( + configDirectory.resolve(Constant.EXTENSIONS_CONFIG_FILE).toString() + )); proxyModuleFactoryManager = new ProxyFactoryModuleManager(); interceptorModuleFactoryManager = new InterceptorModuleFactoryManager(); petepListenerManager = new PetepListenerManager(); @@ -107,16 +114,16 @@ public void load(CommandLineArguments arguments) throws ConfigurationException { // Load proxies. var proxyModuleContainer = new ProxyModuleContainer(ModulesLoader.load( - configDirectory + Constant.PROXIES_CONFIG_FILE, + configDirectory.resolve(Constant.PROXIES_CONFIG_FILE).toString(), proxyModuleFactoryManager)); // Load interceptor module managers. var interceptorModuleContainerC2S = new InterceptorModuleContainer(ModulesLoader.load( - configDirectory + Constant.INTERCEPTORS_C2S_CONFIG_FILE, + configDirectory.resolve(Constant.INTERCEPTORS_C2S_CONFIG_FILE).toString(), interceptorModuleFactoryManager)); var interceptorModuleContainerS2C = new InterceptorModuleContainer(ModulesLoader.load( - configDirectory + Constant.INTERCEPTORS_S2C_CONFIG_FILE, + configDirectory.resolve(Constant.INTERCEPTORS_S2C_CONFIG_FILE).toString(), interceptorModuleFactoryManager)); // Create PETEP manager. @@ -132,27 +139,26 @@ public void load(CommandLineArguments arguments) throws ConfigurationException { * @throws ConfigurationException if the bundle could not be saved because of configuration error */ public void save() throws ConfigurationException { - var configDirectory = - FileUtils.getProjectFileAbsolutePath(Constant.PROJECT_CONFIG_DIRECTORY) + File.separator; + var configDirectory = Path.of(FileUtils.getProjectFileAbsolutePath(Constant.PROJECT_CONFIG_DIRECTORY)); ProjectSaver.save( - configDirectory + Constant.PROJECT_CONFIG_FILE, + configDirectory.resolve(Constant.PROJECT_CONFIG_FILE).toString(), project); ExtensionsSaver.save( - configDirectory + Constant.EXTENSIONS_CONFIG_FILE, + configDirectory.resolve(Constant.EXTENSIONS_CONFIG_FILE).toString(), extensionManager.getList()); ModulesSaver.save( - configDirectory + Constant.PROXIES_CONFIG_FILE, + configDirectory.resolve(Constant.PROXIES_CONFIG_FILE).toString(), petepManager.getProxyModuleContainer().getList()); ModulesSaver.save( - configDirectory + Constant.INTERCEPTORS_C2S_CONFIG_FILE, + configDirectory.resolve(Constant.INTERCEPTORS_C2S_CONFIG_FILE).toString(), petepManager.getInterceptorModuleContainerC2S().getList()); ModulesSaver.save( - configDirectory + Constant.INTERCEPTORS_S2C_CONFIG_FILE, + configDirectory.resolve(Constant.INTERCEPTORS_S2C_CONFIG_FILE).toString(), petepManager.getInterceptorModuleContainerS2C().getList()); } diff --git a/src/main/java/com/warxim/petep/bootstrap/PetepBootstrap.java b/src/main/java/com/warxim/petep/bootstrap/PetepBootstrap.java index e6f5ad9..d3994d0 100644 --- a/src/main/java/com/warxim/petep/bootstrap/PetepBootstrap.java +++ b/src/main/java/com/warxim/petep/bootstrap/PetepBootstrap.java @@ -45,7 +45,7 @@ public void start() throws BootstrapException { try { // Initialize utils. FileUtils.setProjectDirectory( - FileUtils.getApplicationFileAbsolutePath(arguments.getProjectPath())); + FileUtils.getWorkingDirectoryFileAbsolutePath(arguments.getProjectPath())); // Load PETEP bundle. Bundle.getInstance().load(arguments); diff --git a/src/main/java/com/warxim/petep/gui/PetepGui.java b/src/main/java/com/warxim/petep/gui/PetepGui.java index d5ab343..66e8d34 100644 --- a/src/main/java/com/warxim/petep/gui/PetepGui.java +++ b/src/main/java/com/warxim/petep/gui/PetepGui.java @@ -19,7 +19,6 @@ import com.sun.javafx.css.StyleManager; import com.warxim.petep.Bundle; import com.warxim.petep.common.Constant; -import com.warxim.petep.exception.ConfigurationException; import com.warxim.petep.gui.common.GuiConstant; import com.warxim.petep.gui.common.PetepApplication; import com.warxim.petep.gui.dialog.Dialogs; @@ -74,20 +73,9 @@ public void start(Stage stage) { *

Shows dialog to ask user, whether the project should be saved or not.

*/ private void onClose(WindowEvent event) { - var decision = Dialogs.createYesOrNoOrCancelDialog("Save project", "Do you want to save project before closing PETEP?"); - if (decision.isEmpty()) { + var closeProject = Dialogs.createCloseProjectDialog(); + if (!closeProject) { event.consume(); - return; - } - - if (Boolean.TRUE.equals(decision.get())) { - try { - Bundle.getInstance().save(); - } catch (ConfigurationException e) { - event.consume(); - Logger.getGlobal().log(Level.SEVERE, "Could not save project!", e); - Dialogs.createExceptionDialog("Could not save project", "Could not save project!", e); - } } } } diff --git a/src/main/java/com/warxim/petep/gui/controller/ApplicationController.java b/src/main/java/com/warxim/petep/gui/controller/ApplicationController.java index ead0c2d..dc6c127 100644 --- a/src/main/java/com/warxim/petep/gui/controller/ApplicationController.java +++ b/src/main/java/com/warxim/petep/gui/controller/ApplicationController.java @@ -28,13 +28,13 @@ import com.warxim.petep.gui.guide.GuideDialog; import com.warxim.petep.helper.DefaultGuiHelper; import com.warxim.petep.util.GuiUtils; -import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.control.TabPane; +import javafx.stage.Stage; import java.io.IOException; import java.net.URL; @@ -133,7 +133,12 @@ private void onProjectInfoMenuClick(ActionEvent event) { */ @FXML private void onExitMenuClick(ActionEvent event) { - Platform.exit(); + var closeProject = Dialogs.createCloseProjectDialog(); + if (!closeProject) { + return; + } + var stage = (Stage) tabs.getScene().getWindow(); + stage.close(); } /** diff --git a/src/main/java/com/warxim/petep/gui/dialog/Dialogs.java b/src/main/java/com/warxim/petep/gui/dialog/Dialogs.java index d393f30..cd91558 100644 --- a/src/main/java/com/warxim/petep/gui/dialog/Dialogs.java +++ b/src/main/java/com/warxim/petep/gui/dialog/Dialogs.java @@ -16,7 +16,9 @@ */ package com.warxim.petep.gui.dialog; +import com.warxim.petep.Bundle; import com.warxim.petep.common.Constant; +import com.warxim.petep.exception.ConfigurationException; import com.warxim.petep.extension.PetepAPI; import com.warxim.petep.gui.GuiBundle; import javafx.event.ActionEvent; @@ -357,6 +359,28 @@ public static void createNewVersionDialog(String newVersion) { alert.showAndWait(); } + /** + * Create close project dialog + * @return {@code} true if project should be closed + */ + public static boolean createCloseProjectDialog() { + var decision = Dialogs.createYesOrNoOrCancelDialog("Save project", "Do you want to save project before closing PETEP?"); + if (decision.isEmpty()) { + return false; + } + + if (Boolean.TRUE.equals(decision.get())) { + try { + Bundle.getInstance().save(); + } catch (ConfigurationException e) { + Logger.getGlobal().log(Level.SEVERE, "Could not save project!", e); + Dialogs.createExceptionDialog("Could not save project", "Could not save project!", e); + return false; + } + } + return true; + } + /** * Opens PETEP website. */ diff --git a/src/main/java/com/warxim/petep/util/FileUtils.java b/src/main/java/com/warxim/petep/util/FileUtils.java index 1160291..f8ed68c 100644 --- a/src/main/java/com/warxim/petep/util/FileUtils.java +++ b/src/main/java/com/warxim/petep/util/FileUtils.java @@ -27,8 +27,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Comparator; import java.util.logging.Logger; -import java.util.stream.Stream; /** * File utils. @@ -66,13 +66,7 @@ public static File getApplicationFile(String path) { * @return Absolute path of application file */ public static String getApplicationFileAbsolutePath(String path) { - if (Paths.get(path).isAbsolute()) { - // Absolute path - return path; - } - - // Relative path - return Paths.get(getApplicationDirectory()).resolve(path).normalize().toString(); + return getFileAbsolutePath(getApplicationDirectory(), path); } /** @@ -81,17 +75,7 @@ public static String getApplicationFileAbsolutePath(String path) { * @return Path relative to application directory */ public static String applicationRelativize(Path path) { - try { - var applicationDirectoryPath = Paths.get(getApplicationDirectory()); - - // Resolve path - path = applicationDirectoryPath.resolve(path).normalize(); - - // Relativize the path - return applicationDirectoryPath.relativize(path).toString(); - } catch (IllegalArgumentException e) { - return path.toString(); - } + return relativize(Path.of(getApplicationDirectory()), path); } /** @@ -103,23 +87,31 @@ public static String applicationRelativize(String path) { return applicationRelativize(Paths.get(path)); } + /** + * Relativizes given path to working directory path + * @param path Absolute path to relativize + * @return Path relative to working directory + */ + public static String workingDirectoryRelativize(Path path) { + return relativize(Path.of(getWorkingDirectory()), path); + } + + /** + * Relativizes given path to working directory path + * @param path Absolute path to relativize + * @return Path relative to working directory + */ + public static String workingDirectoryRelativize(String path) { + return workingDirectoryRelativize(Path.of(path)); + } + /** * Relativizes given path to project directory path * @param path Absolute path to relativize * @return Path relative to project directory */ public static String projectRelativize(Path path) { - try { - var projectDirectoryPath = Paths.get(getProjectDirectory()); - - // Resolve path - path = projectDirectoryPath.resolve(path).normalize(); - - // Relativize the path - return projectDirectoryPath.relativize(path).toString(); - } catch (IllegalArgumentException e) { - return path.toString(); - } + return relativize(Path.of(getProjectDirectory()), path); } /** @@ -131,6 +123,23 @@ public static String projectRelativize(String path) { return projectRelativize(Paths.get(path)); } + /** + * Relativizes given path to root directory path + * @param path Absolute path to relativize + * @return Path relative to root directory + */ + public static String relativize(Path rootDirectoryPath, Path path) { + try { + // Resolve path + path = rootDirectoryPath.resolve(path).normalize(); + + // Relativize the path + return rootDirectoryPath.relativize(path).toString(); + } catch (IllegalArgumentException e) { + return path.toString(); + } + } + /** * Obtains project file. * @param path Path relative to project directory @@ -146,13 +155,16 @@ public static File getProjectFile(String path) { * @return Absolute path of project file */ public static String getProjectFileAbsolutePath(String path) { - if (Paths.get(path).isAbsolute()) { - // Absolute path - return path; - } + return getFileAbsolutePath(getProjectDirectory(), path); + } - // Relative path - return Paths.get(getProjectDirectory()).resolve(path).normalize().toString(); + /** + * Obtains working directory file absolute path + * @param path Path relative to working directory + * @return Absolute path of working directory file + */ + public static String getWorkingDirectoryFileAbsolutePath(String path) { + return getFileAbsolutePath(getWorkingDirectory(), path); } /** @@ -179,6 +191,14 @@ public static String getProjectDirectory() { return System.getProperty("user.dir"); } + /** + * Obtains working directory. + * @return Current working directory + */ + public static String getWorkingDirectory() { + return System.getProperty("user.dir"); + } + /** * Sets working directory (used internally). * @param directory Directory to be set @@ -213,13 +233,32 @@ public static void copyDirectory(String source, String destination) throws IOExc * @throws IOException If the copy failed */ public static void copyDirectory(Path source, Path destination) throws IOException { - try (Stream stream = Files.walk(source)) { + try (var stream = Files.walk(source)) { + var destFile = destination.toFile(); + if (!destFile.exists() && !destFile.mkdirs()) { + throw new IOException(String.format("Could not create directory '%s'", destFile.getAbsolutePath())); + } stream.forEach(src -> copy(src, destination.resolve(source.relativize(src)))); } catch (UncheckedIOException e) { throw e.getCause(); } } + /** + * Deletes directories recursively + * @param directory Directory to delete (including content) + * @return {@code true} if the deletion was successful + */ + public static boolean deleteDirectories(String directory) { + try (var stream = Files.walk(Path.of(directory))) { + return stream.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .allMatch(File::delete); + } catch (IOException e) { + return false; + } + } + /** * Copies from source to destination and replace existing. * @param source Source path @@ -241,11 +280,13 @@ private static String initApplicationDirectory() { try { var path = Paths.get(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + // PETEP run from lib/petep.jar if (path.toString().toLowerCase().endsWith(".jar")) { return path.getParent().getParent().toString(); } - return getProjectDirectory(); + // Fallback for working directory in IDE + return Paths.get(".").toAbsolutePath().normalize().toString(); } catch (URISyntaxException e) { Logger.getGlobal().severe(e.getMessage()); } diff --git a/src/main/java/com/warxim/petep/wizard/configuration/WizardConfigurationLoader.java b/src/main/java/com/warxim/petep/wizard/configuration/WizardConfigurationLoader.java index 486527c..521f62f 100644 --- a/src/main/java/com/warxim/petep/wizard/configuration/WizardConfigurationLoader.java +++ b/src/main/java/com/warxim/petep/wizard/configuration/WizardConfigurationLoader.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.time.Instant; @@ -86,12 +87,12 @@ public static List load(String path) { */ private static Optional loadProjectDecorator(JsonElement element) { try { - var configFile = FileUtils.getApplicationFileAbsolutePath( - element.getAsString() - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator - + Constant.PROJECT_CONFIG_FILE); + var configFile = FileUtils.getWorkingDirectoryFileAbsolutePath( + Path.of(element.getAsString()) + .resolve(Constant.PROJECT_CONFIG_DIRECTORY) + .resolve(Constant.PROJECT_CONFIG_FILE) + .toString() + ); // Load project information. var project = ProjectLoader.load(configFile); diff --git a/src/main/java/com/warxim/petep/wizard/controller/WizardController.java b/src/main/java/com/warxim/petep/wizard/controller/WizardController.java index c8460bf..be58f42 100644 --- a/src/main/java/com/warxim/petep/wizard/controller/WizardController.java +++ b/src/main/java/com/warxim/petep/wizard/controller/WizardController.java @@ -44,6 +44,7 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.time.Instant; import java.util.ResourceBundle; import java.util.logging.Level; @@ -74,7 +75,9 @@ public final class WizardController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { // Create observable list of projects. - projects = FXCollections.observableList(WizardConfigurationLoader.load(Constant.WIZARD_CONFIG_FILE)); + projects = FXCollections.observableList(WizardConfigurationLoader.load( + FileUtils.getWorkingDirectoryFileAbsolutePath(Constant.WIZARD_CONFIG_FILE) + )); // Initialize columns. nameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); @@ -105,7 +108,10 @@ private void onNewButtonClick(ActionEvent event) { projectsTable.getSelectionModel().select(maybeProject.get()); // Save wizard configuration. - WizardConfigurationSaver.save(Constant.WIZARD_CONFIG_FILE, projects); + WizardConfigurationSaver.save( + FileUtils.getWorkingDirectoryFileAbsolutePath(Constant.WIZARD_CONFIG_FILE), + projects + ); } catch (IOException e) { Logger.getGlobal().log(Level.SEVERE, "Exception during openning of project dialog", e); } @@ -134,7 +140,10 @@ private void onEditButtonClick(ActionEvent event) { projectsTable.getSelectionModel().select(maybeProject.get()); // Save wizard configuration. - WizardConfigurationSaver.save(Constant.WIZARD_CONFIG_FILE, projects); + WizardConfigurationSaver.save( + FileUtils.getWorkingDirectoryFileAbsolutePath(Constant.WIZARD_CONFIG_FILE), + projects + ); } catch (IOException e) { Logger.getGlobal().log(Level.SEVERE, "Exception during openning of project dialog", e); } catch (ConfigurationException e) { @@ -171,7 +180,7 @@ private void onOpenButtonClick(ActionEvent event) { // Choose project directory. var directoryChooser = new DirectoryChooser(); directoryChooser.setTitle("Open project"); - directoryChooser.setInitialDirectory(new File(System.getProperty("user.dir"))); + directoryChooser.setInitialDirectory(new File(FileUtils.getWorkingDirectory())); var directory = directoryChooser.showDialog(null); if (directory == null) { @@ -180,15 +189,14 @@ private void onOpenButtonClick(ActionEvent event) { // Load project. try { - var configFile = directory.getPath() - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator - + Constant.PROJECT_CONFIG_FILE; + var configFile = Path.of(directory.getPath()) + .resolve(Constant.PROJECT_CONFIG_DIRECTORY) + .resolve(Constant.PROJECT_CONFIG_FILE) + .toString(); var project = ProjectLoader.load(configFile); - var relativizedPath = FileUtils.applicationRelativize(directory.getPath()); + var relativizedPath = FileUtils.workingDirectoryRelativize(directory.getPath()); var lastModified = Instant.ofEpochMilli(new File(configFile).lastModified()); // Add project to table. @@ -199,7 +207,10 @@ private void onOpenButtonClick(ActionEvent event) { lastModified)); // Save wizard configuration. - WizardConfigurationSaver.save(Constant.WIZARD_CONFIG_FILE, projects); + WizardConfigurationSaver.save( + FileUtils.getWorkingDirectoryFileAbsolutePath(Constant.WIZARD_CONFIG_FILE), + projects + ); } catch (ConfigurationException e) { Dialogs.createExceptionDialog("Project could not be loaded", "Project could not be loaded!", e); @@ -228,7 +239,10 @@ private void onRemoveButtonClick(ActionEvent event) { projects.remove(project); // Save wizard configuration. - WizardConfigurationSaver.save(Constant.WIZARD_CONFIG_FILE, projects); + WizardConfigurationSaver.save( + FileUtils.getWorkingDirectoryFileAbsolutePath(Constant.WIZARD_CONFIG_FILE), + projects + ); } /** diff --git a/src/main/java/com/warxim/petep/wizard/dialog/EditProjectDialog.java b/src/main/java/com/warxim/petep/wizard/dialog/EditProjectDialog.java index f7693da..bdfe348 100644 --- a/src/main/java/com/warxim/petep/wizard/dialog/EditProjectDialog.java +++ b/src/main/java/com/warxim/petep/wizard/dialog/EditProjectDialog.java @@ -25,8 +25,8 @@ import com.warxim.petep.wizard.project.WizardProjectDecorator; import javafx.collections.FXCollections; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.time.Instant; import java.util.logging.Level; import java.util.logging.Logger; @@ -46,11 +46,10 @@ public EditProjectDialog(WizardProjectDecorator project) throws IOException, Con // Load extensions for the project var list = WizardExtensionsLoader.load( - FileUtils.getApplicationFileAbsolutePath(project.getPath()) - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator - + Constant.EXTENSIONS_CONFIG_FILE); + Path.of(FileUtils.getWorkingDirectoryFileAbsolutePath(project.getPath())) + .resolve(Constant.PROJECT_CONFIG_DIRECTORY) + .resolve(Constant.EXTENSIONS_CONFIG_FILE) + .toString()); // Fill inputs nameInput.setText(project.getName()); diff --git a/src/main/java/com/warxim/petep/wizard/dialog/NewProjectDialog.java b/src/main/java/com/warxim/petep/wizard/dialog/NewProjectDialog.java index 1821e54..47c0119 100644 --- a/src/main/java/com/warxim/petep/wizard/dialog/NewProjectDialog.java +++ b/src/main/java/com/warxim/petep/wizard/dialog/NewProjectDialog.java @@ -32,6 +32,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -61,8 +63,8 @@ public NewProjectDialog() throws IOException { extensionsList.setItems(extensions); - initPresets(); initToggles(); + initPresets(); } /** @@ -95,6 +97,13 @@ protected WizardProjectDecorator obtainResult() { private void initPresets() { var presetsDirectory = FileUtils.getApplicationFile(Constant.PRESETS_DIRECTORY); var presetsFiles = presetsDirectory.listFiles(); + if (presetsFiles == null || presetsFiles.length == 0) { + // If there are no presets, disable the option and use custom project type + presetInput.setDisable(true); + presetRadioInput.setDisable(true); + customRadioInput.setSelected(true); + return; + } var presets = Arrays.stream(presetsFiles) .filter(File::isDirectory) .map(File::getName) @@ -144,7 +153,8 @@ private static void createPresetProject( throws IOException, ConfigurationException { FileUtils.copyDirectory( new File(FileUtils.getApplicationFile(Constant.PRESETS_DIRECTORY), presetPath).getPath(), - path); + FileUtils.getWorkingDirectoryFileAbsolutePath(path) + ); saveProject(path, projectDecorator.getProject()); } @@ -158,33 +168,31 @@ private static void createCustomProject( List extensions) throws ConfigurationException, IOException { // Create project directory. + path = FileUtils.getWorkingDirectoryFileAbsolutePath(path); WizardProjectDirectoryCreator.create(path); // Save project. saveProject(path, projectDecorator.getProject()); - saveExtensions(path,extensions); + saveExtensions(path, extensions); - var configDirectory = path - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator; + var configDirectory = Path.of(path).resolve(Constant.PROJECT_CONFIG_DIRECTORY); // Create proxies.json if it does not exist. - var file = configDirectory + Constant.PROXIES_CONFIG_FILE; - if (!new File(file).exists()) { - ModulesSaver.save(file, new ArrayList<>()); + var file = configDirectory.resolve(Constant.PROXIES_CONFIG_FILE); + if (!Files.exists(file)) { + ModulesSaver.save(file.toString(), new ArrayList<>()); } // Create interceptors-C2S.json if it does not exist. - file = configDirectory + Constant.INTERCEPTORS_C2S_CONFIG_FILE; - if (!new File(file).exists()) { - ModulesSaver.save(file, new ArrayList<>()); + file = configDirectory.resolve(Constant.INTERCEPTORS_C2S_CONFIG_FILE); + if (!Files.exists(file)) { + ModulesSaver.save(file.toString(), new ArrayList<>()); } // Create interceptors-S2C.json if it does not exist. - file = configDirectory + Constant.INTERCEPTORS_S2C_CONFIG_FILE; - if (!new File(file).exists()) { - ModulesSaver.save(file, new ArrayList<>()); + file = configDirectory.resolve(Constant.INTERCEPTORS_S2C_CONFIG_FILE); + if (!Files.exists(file)) { + ModulesSaver.save(file.toString(), new ArrayList<>()); } } } diff --git a/src/main/java/com/warxim/petep/wizard/dialog/ProjectDialog.java b/src/main/java/com/warxim/petep/wizard/dialog/ProjectDialog.java index 0be0bef..4c98db2 100644 --- a/src/main/java/com/warxim/petep/wizard/dialog/ProjectDialog.java +++ b/src/main/java/com/warxim/petep/wizard/dialog/ProjectDialog.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.List; /** @@ -87,17 +88,17 @@ protected final void onAddInternalButtonClick(ActionEvent event) { } // Choose from internal extensions. - var dialog = new ChoiceDialog(Constant.INTERNAL_EXTENSIONS.get(0), Constant.INTERNAL_EXTENSIONS); - - dialog.setTitle("Add internal extension"); - dialog.setHeaderText("Add internal extension"); - dialog.setContentText("Extension:"); - - // Add extension to list. - var result = dialog.showAndWait(); - if (result.isPresent()) { - extensions.add(new WizardProjectExtension(result.get(), null, null)); + var result = Dialogs.createChoiceDialog( + "Add internal extension", + "Extension:", + Constant.INTERNAL_EXTENSIONS, + Constant.INTERNAL_EXTENSIONS.get(0) + ); + if (result.isEmpty()) { + return; } + + addExtension(result.get()); } /** @@ -108,7 +109,7 @@ protected final void onAddExternalButtonClick(ActionEvent event) { // Choose extension file. var fileChooser = new FileChooser(); fileChooser.setTitle("Open project"); - fileChooser.setInitialDirectory(new File(System.getProperty("user.dir"))); + fileChooser.setInitialDirectory(new File(FileUtils.getApplicationDirectory())); fileChooser.getExtensionFilters() .add(new FileChooser.ExtensionFilter("PETEP jar extensions (*.jar)", "*.jar")); @@ -118,7 +119,19 @@ protected final void onAddExternalButtonClick(ActionEvent event) { } // Add extension to list. - extensions.add(new WizardProjectExtension(FileUtils.applicationRelativize(file.getPath()), null, null)); + addExtension(FileUtils.applicationRelativize(file.getPath())); + } + + /** + * Adds extension to the extension list if not already present + */ + private void addExtension(String newExtensionPath) { + var alreadyExists = extensions.stream() + .anyMatch(extension -> extension.getPath().equals(newExtensionPath)); + if (alreadyExists) { + return; + } + extensions.add(new WizardProjectExtension(newExtensionPath, null, null)); } /** @@ -131,7 +144,7 @@ protected final void onRemoveButtonClick(ActionEvent event) { return; } - if (!Dialogs.createYesOrNoDialog("Are you sure?", + if (!Dialogs.createYesOrNoDialog("Remove extension", "Do you really want to remove the extension '" + extension.getPath() + "' from the project? (Extension data stored in the configuration will be lost!)")) { return; @@ -162,7 +175,7 @@ protected final boolean isValid() { private void onPathButtonClick(ActionEvent event) { var directoryChooser = new DirectoryChooser(); directoryChooser.setTitle("Save project"); - directoryChooser.setInitialDirectory(new File(System.getProperty("user.dir"))); + directoryChooser.setInitialDirectory(new File(FileUtils.getWorkingDirectory())); var directory = directoryChooser.showDialog(null); if (directory == null) { @@ -176,18 +189,17 @@ private void onPathButtonClick(ActionEvent event) { return; } - pathInput.setText(FileUtils.applicationRelativize(directory.getPath())); + pathInput.setText(FileUtils.workingDirectoryRelativize(directory.getPath())); } /** * Saves project and its extension configuration. */ protected static void saveProject(String path, Project project) throws ConfigurationException { - var projectConfigPath = FileUtils.getApplicationFileAbsolutePath(path) - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator - + Constant.PROJECT_CONFIG_FILE; + var projectConfigPath = Path.of(FileUtils.getWorkingDirectoryFileAbsolutePath(path)) + .resolve(Constant.PROJECT_CONFIG_DIRECTORY) + .resolve(Constant.PROJECT_CONFIG_FILE) + .toString(); ProjectSaver.save(projectConfigPath, project); } @@ -196,11 +208,10 @@ protected static void saveProject(String path, Project project) throws Configura */ protected static void saveExtensions(String path, List extensions) throws ConfigurationException { - var projectExtensionsPath = FileUtils.getApplicationFileAbsolutePath(path) - + File.separator - + Constant.PROJECT_CONFIG_DIRECTORY - + File.separator - + Constant.EXTENSIONS_CONFIG_FILE; + var projectExtensionsPath = Path.of(FileUtils.getWorkingDirectoryFileAbsolutePath(path)) + .resolve(Constant.PROJECT_CONFIG_DIRECTORY) + .resolve(Constant.EXTENSIONS_CONFIG_FILE) + .toString(); WizardExtensionsSaver.save(projectExtensionsPath, extensions); } } diff --git a/src/test/java/com/warxim/petep/extension/internal/history/service/HistoryTestBase.java b/src/test/java/com/warxim/petep/extension/internal/history/service/HistoryTestBase.java index dc73d3c..9b2ceb6 100644 --- a/src/test/java/com/warxim/petep/extension/internal/history/service/HistoryTestBase.java +++ b/src/test/java/com/warxim/petep/extension/internal/history/service/HistoryTestBase.java @@ -8,11 +8,11 @@ import com.warxim.petep.extension.internal.history.model.HistoricProxy; import com.warxim.petep.extension.internal.history.repository.CachedDatabaseHistoryRepository; import com.warxim.petep.extension.internal.history.repository.DatabaseHistoryRepository; +import com.warxim.petep.util.FileUtils; import lombok.extern.java.Log; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeSuite; -import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -20,12 +20,15 @@ import java.sql.SQLException; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; @Log public class HistoryTestBase { - protected static final String TEST_DIRECTORY = "./test_temp_directory"; + protected static final String TEST_DIRECTORY = "./tmp_test_history"; protected static final Path TEST_DIRECTORY_PATH = Path.of(TEST_DIRECTORY); private List services = new ArrayList<>(); @@ -33,6 +36,7 @@ public class HistoryTestBase { @BeforeSuite(alwaysRun = true) public void beforeSuite() throws IOException { log.info("Creating test directory: " + TEST_DIRECTORY); + FileUtils.deleteDirectories(TEST_DIRECTORY); Files.createDirectories(TEST_DIRECTORY_PATH); } @@ -43,10 +47,7 @@ public void afterSuite() throws IOException { } log.info("Deleting test directory: " + TEST_DIRECTORY); - Files.walk(TEST_DIRECTORY_PATH) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + FileUtils.deleteDirectories(TEST_DIRECTORY); } protected static String getTestFilePath(String path) { diff --git a/src/test/java/com/warxim/petep/test/base/util/TestUtils.java b/src/test/java/com/warxim/petep/test/base/util/TestUtils.java index abc16d0..69fbb83 100644 --- a/src/test/java/com/warxim/petep/test/base/util/TestUtils.java +++ b/src/test/java/com/warxim/petep/test/base/util/TestUtils.java @@ -41,7 +41,7 @@ public static T getWithRetries(Supplier supplier, int retryCount, int ret if (value != null) { return value; } - TestUtils.sleep(retryInterval); + sleep(retryInterval); } throw new AssertionError("Could not get value!"); } diff --git a/src/test/java/com/warxim/petep/util/FileUtilsTest.java b/src/test/java/com/warxim/petep/util/FileUtilsTest.java index ac4e0e3..f35f22c 100644 --- a/src/test/java/com/warxim/petep/util/FileUtilsTest.java +++ b/src/test/java/com/warxim/petep/util/FileUtilsTest.java @@ -39,6 +39,7 @@ public void getApplicationFileAbsolutePathTest(String directory, String path, St try (var mocked = mockStatic(FileUtils.class)) { mocked.when(FileUtils::getApplicationDirectory).thenReturn(directory); mocked.when(() -> FileUtils.getApplicationFileAbsolutePath(anyString())).thenCallRealMethod(); + mocked.when(() -> FileUtils.getFileAbsolutePath(anyString(), anyString())).thenCallRealMethod(); mocked.when(() -> FileUtils.getApplicationFile(anyString())).thenCallRealMethod(); assertThat(unifyPath(FileUtils.getApplicationFileAbsolutePath(path))) @@ -57,6 +58,7 @@ public void getProjectFileAbsolutePathTest(String directory, String path, String try (var mocked = mockStatic(FileUtils.class)) { mocked.when(FileUtils::getProjectDirectory).thenReturn(directory); mocked.when(() -> FileUtils.getProjectFileAbsolutePath(anyString())).thenCallRealMethod(); + mocked.when(() -> FileUtils.getFileAbsolutePath(anyString(), anyString())).thenCallRealMethod(); mocked.when(() -> FileUtils.getProjectFile(anyString())).thenCallRealMethod(); assertThat(unifyPath(FileUtils.getProjectFileAbsolutePath(path))) @@ -67,6 +69,21 @@ public void getProjectFileAbsolutePathTest(String directory, String path, String } } + @Test(dataProvider = "paths") + public void getWorkingDirectoryFileAbsolutePathTest(String directory, String path, String expectedResult, boolean windowsOnly) { + if (windowsOnly && !isWindows()) { + return; + } + try (var mocked = mockStatic(FileUtils.class)) { + mocked.when(FileUtils::getWorkingDirectory).thenReturn(directory); + mocked.when(() -> FileUtils.getWorkingDirectoryFileAbsolutePath(anyString())).thenCallRealMethod(); + mocked.when(() -> FileUtils.getFileAbsolutePath(anyString(), anyString())).thenCallRealMethod(); + + assertThat(unifyPath(FileUtils.getWorkingDirectoryFileAbsolutePath(path))) + .isEqualTo(unifyPath(expectedResult)); + } + } + @DataProvider(name = "pathsToRelativize") public Object[][] pathsToRelativizeProvider() { var winDirectory = "D:/Projects/Test"; @@ -93,6 +110,7 @@ public void applicationRelativizeTest(String directory, String path, String expe mocked.when(FileUtils::getApplicationDirectory).thenReturn(directory); mocked.when(() -> FileUtils.applicationRelativize(anyString())).thenCallRealMethod(); mocked.when(() -> FileUtils.applicationRelativize(any(Path.class))).thenCallRealMethod(); + mocked.when(() -> FileUtils.relativize(any(Path.class), any(Path.class))).thenCallRealMethod(); assertThat(unifyPath(FileUtils.applicationRelativize(path))) .isEqualTo(unifyPath(expectedResult)); @@ -102,6 +120,44 @@ public void applicationRelativizeTest(String directory, String path, String expe } } + @Test(dataProvider = "pathsToRelativize") + public void projectRelativizeTest(String directory, String path, String expectedResult, boolean windowsOnly) { + if (windowsOnly && !isWindows()) { + return; + } + try (var mocked = mockStatic(FileUtils.class)) { + mocked.when(FileUtils::getProjectDirectory).thenReturn(directory); + mocked.when(() -> FileUtils.projectRelativize(anyString())).thenCallRealMethod(); + mocked.when(() -> FileUtils.projectRelativize(any(Path.class))).thenCallRealMethod(); + mocked.when(() -> FileUtils.relativize(any(Path.class), any(Path.class))).thenCallRealMethod(); + + assertThat(unifyPath(FileUtils.projectRelativize(path))) + .isEqualTo(unifyPath(expectedResult)); + + assertThat(unifyPath(FileUtils.projectRelativize(Path.of(path)))) + .isEqualTo(unifyPath(expectedResult)); + } + } + + @Test(dataProvider = "pathsToRelativize") + public void workingDirectoryRelativizeTest(String directory, String path, String expectedResult, boolean windowsOnly) { + if (windowsOnly && !isWindows()) { + return; + } + try (var mocked = mockStatic(FileUtils.class)) { + mocked.when(FileUtils::getWorkingDirectory).thenReturn(directory); + mocked.when(() -> FileUtils.workingDirectoryRelativize(anyString())).thenCallRealMethod(); + mocked.when(() -> FileUtils.workingDirectoryRelativize(any(Path.class))).thenCallRealMethod(); + mocked.when(() -> FileUtils.relativize(any(Path.class), any(Path.class))).thenCallRealMethod(); + + assertThat(unifyPath(FileUtils.workingDirectoryRelativize(path))) + .isEqualTo(unifyPath(expectedResult)); + + assertThat(unifyPath(FileUtils.workingDirectoryRelativize(Path.of(path)))) + .isEqualTo(unifyPath(expectedResult)); + } + } + private static boolean isWindows() { return System.getProperty("os.name").toLowerCase().contains("win"); }