diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java index ca6c3b86bd2073aee7000fd77c7b833eaf10fb0e..1e423659f6b88228c64a8976bd18568f3509a3bd 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationController.java @@ -30,7 +30,7 @@ public class MigrationController extends BaseController { public static final String ERROR = "error"; public static final String MIGRATION_SETTINGS = "migrationSettings"; - public static final String STATUS_TEXT_KEY = "statusTextKey"; + public static final String STATUS = "status"; } private static class ReturnValues @@ -90,24 +90,14 @@ public class MigrationController extends BaseController return ReturnValues.MIGRATION_SETTINGS; } - try - { - final MigrationArguments migrationArguments = new MigrationArguments.MigrationArgumentBuilder() - .withSourceUrl(migrationService.getDatabaseFromPreviousVersionPathWithoutExtension().toString()) - .withDestinationUrl(migrationSettings.hostname(), migrationSettings.port(), migrationSettings.databaseName()) - .withDestinationCredentials(migrationSettings.username(), migrationSettings.password()) - .build(); - // TODO: run non-blocking and redirect to progress page - migrationService.runMigration(migrationArguments); - } - catch(MigrationException | IOException e) - { - WebRequestUtils.putNotification(request, new Notification(Localization.getString("notification.migration.error", e.getMessage()), NotificationType.ERROR)); - return ReturnValues.MIGRATION_SETTINGS; - } + final MigrationArguments migrationArguments = new MigrationArguments.MigrationArgumentBuilder() + .withSourceUrl(migrationService.getDatabaseFromPreviousVersionPathWithoutExtension().toString()) + .withDestinationUrl(migrationSettings.hostname(), migrationSettings.port(), migrationSettings.databaseName()) + .withDestinationCredentials(migrationSettings.username(), migrationSettings.password()) + .build(); + migrationService.startMigration(migrationArguments); - // TODO: redirect to success page - return ReturnValues.MIGRATION_SETTINGS; + return ReturnValues.STATUS; } @GetMapping("/status") @@ -119,7 +109,7 @@ public class MigrationController extends BaseController @GetMapping("/getStatus") public String getMigrationStatus(Model model) { - model.addAttribute(ModelAttributes.STATUS_TEXT_KEY, "migration.status.running"); + model.addAttribute(ModelAttributes.STATUS, migrationService.getMigrationStatus()); return ReturnValues.STATUS_FRAGMENT; } } \ No newline at end of file diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java index 69ef8050a30fbd3eac9df365143194266aeaa77e..c38fe2a6875adf1df654ac3aadf259c1f72591b8 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationService.java @@ -8,31 +8,19 @@ import de.deadlocker8.budgetmaster.templates.TemplateRepository; import de.deadlocker8.budgetmaster.transactions.TransactionRepository; import de.deadlocker8.budgetmaster.utils.DatabaseConfigurationProperties; import de.deadlocker8.budgetmaster.utils.Mappings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.Date; @Service public class MigrationService { - private static final Logger LOGGER = LoggerFactory.getLogger(MigrationService.class); - public static final String PREVIOUS_DATABASE_FILE_NAME = "budgetmaster.mv.db"; public static final String PREVIOUS_DATABASE_FILE_NAME_WITHOUT_EXTENSION = "budgetmaster"; - private static final String BUDGET_MASTER_MIGRATOR_JAR = "BudgetMasterDatabaseMigrator.jar"; private final SettingsService settingsService; private final Path applicationSupportFolder; private final AccountRepository accountRepository; @@ -40,9 +28,12 @@ public class MigrationService private final TransactionRepository transactionRepository; private final TemplateRepository templateRepository; private final DatabaseConfigurationProperties databaseConfig; + private final TaskScheduler scheduler; + + private final MigrationTask migrationTask; @Autowired - public MigrationService(SettingsService settingsService, Path applicationSupportFolder, AccountRepository accountRepository, CategoryRepository categoryRepository, TransactionRepository transactionRepository, TemplateRepository templateRepository, DatabaseConfigurationProperties databaseConfig) + public MigrationService(SettingsService settingsService, Path applicationSupportFolder, AccountRepository accountRepository, CategoryRepository categoryRepository, TransactionRepository transactionRepository, TemplateRepository templateRepository, DatabaseConfigurationProperties databaseConfig, TaskScheduler scheduler) { this.settingsService = settingsService; this.applicationSupportFolder = applicationSupportFolder; @@ -51,6 +42,8 @@ public class MigrationService this.transactionRepository = transactionRepository; this.templateRepository = templateRepository; this.databaseConfig = databaseConfig; + this.migrationTask = new MigrationTask(applicationSupportFolder); + this.scheduler = scheduler; } public MigrationSettings getPrefilledMigrationSettings() @@ -114,85 +107,14 @@ public class MigrationService return Files.exists(getDatabaseFromPreviousVersionPath()); } - public String runMigration(MigrationArguments migrationArguments) throws MigrationException, IOException - { - final Path migratorPath = extractMigrator(); - try - { - return runMigrator(migratorPath, migrationArguments); - } - finally - { - Files.deleteIfExists(migratorPath); - } - } - - private Path extractMigrator() throws MigrationException - { - final Path destinationPath = applicationSupportFolder.resolve(BUDGET_MASTER_MIGRATOR_JAR); - - try - { - Files.copy(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(BUDGET_MASTER_MIGRATOR_JAR)), destinationPath, StandardCopyOption.REPLACE_EXISTING); - return destinationPath; - } - catch(IOException e) - { - throw new MigrationException(MessageFormat.format("Could not copy migrator to {0}", destinationPath), e); - } - } - - private String runMigrator(Path migratorPath, MigrationArguments migrationArguments) throws MigrationException + public void startMigration(MigrationArguments migrationArguments) { - final String javaCommand = determineJavaCommand(); - - final List<String> command = new ArrayList<>(); - command.add(MessageFormat.format("\"{0}\"", javaCommand)); - command.add("-jar"); - command.add(migratorPath.toString()); - command.addAll(migrationArguments.getArguments()); - LOGGER.debug("Starting migration with command: {}", command); - - try - { - final ProcessBuilder processBuilder = new ProcessBuilder(command).redirectErrorStream(true); - final Process process = processBuilder.start(); - - final StringBuilder collectedStdout = new StringBuilder(); - try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) - { - while(true) - { - String line = in.readLine(); - if(line == null) - { - break; - } - - LOGGER.debug("[MIGRATOR] {}", line); - - collectedStdout.append(line); - collectedStdout.append("\n"); - } - } - - LOGGER.debug("Migration process finished"); - return collectedStdout.toString(); - } - catch(IOException e) - { - throw new MigrationException("Error during migration process", e); - } + migrationTask.setMigrationArguments(migrationArguments); + scheduler.schedule(migrationTask, new Date()); } - private String determineJavaCommand() throws MigrationException + public MigrationStatus getMigrationStatus() { - final Optional<String> commandResultOptional = ProcessHandle.current().info().command(); - if(commandResultOptional.isEmpty()) - { - throw new MigrationException("Could not determine java executable"); - } - - return commandResultOptional.get().replace("\\", "/"); + return migrationTask.getMigrationStatus(); } } diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationStatus.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..61e49a05bb461b2a1761ca899fc93074eba112a9 --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationStatus.java @@ -0,0 +1,29 @@ +package de.deadlocker8.budgetmaster.migration; + +public enum MigrationStatus +{ + NOT_RUNNING("migration.status.not.running"), + RUNNING("migration.status.running"), + SUCCESS("migration.status.success"), + ERROR("migration.status.error"); + + private final String localizationKey; + + MigrationStatus(String localizationKey) + { + this.localizationKey = localizationKey; + } + + public String getLocalizationKey() + { + return localizationKey; + } + + @Override + public String toString() + { + return "MigrationStatus{" + + "localizationKey='" + localizationKey + '\'' + + "} " + super.toString(); + } +} diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationTask.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationTask.java new file mode 100644 index 0000000000000000000000000000000000000000..c7a4de6a90ffc2645c9f830daf1f4e8f219f8e6a --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/migration/MigrationTask.java @@ -0,0 +1,155 @@ +package de.deadlocker8.budgetmaster.migration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class MigrationTask implements Runnable +{ + private static final Logger LOGGER = LoggerFactory.getLogger(MigrationTask.class); + private static final String BUDGET_MASTER_MIGRATOR_JAR = "BudgetMasterDatabaseMigrator.jar"; + + private final Path applicationSupportFolder; + + private MigrationArguments migrationArguments; + private MigrationStatus migrationStatus; + + public MigrationTask(Path applicationSupportFolder) + { + this.applicationSupportFolder = applicationSupportFolder; + this.migrationStatus = MigrationStatus.NOT_RUNNING; + } + + public void setMigrationArguments(MigrationArguments migrationArguments) + { + this.migrationArguments = migrationArguments; + } + + @Override + public void run() + { + if(migrationArguments == null) + { + throw new RuntimeException("No migration arguments set!"); + } + + if(migrationStatus != MigrationStatus.NOT_RUNNING) + { + throw new RuntimeException("Migration already performed!"); + } + + LOGGER.debug("Start migration..."); + setMigrationStatus(MigrationStatus.RUNNING); + + try + { + final Path migratorPath = extractMigrator(); + try + { + runMigrator(migratorPath, migrationArguments); + } + finally + { + Files.deleteIfExists(migratorPath); + } + + setMigrationStatus(MigrationStatus.SUCCESS); + } + catch(MigrationException | IOException e) + { + LOGGER.error("Migration failed", e); + setMigrationStatus(MigrationStatus.ERROR); + } + } + + private Path extractMigrator() throws MigrationException + { + final Path destinationPath = applicationSupportFolder.resolve(BUDGET_MASTER_MIGRATOR_JAR); + + try + { + Files.copy(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(BUDGET_MASTER_MIGRATOR_JAR)), destinationPath, StandardCopyOption.REPLACE_EXISTING); + return destinationPath; + } + catch(IOException e) + { + throw new MigrationException(MessageFormat.format("Could not copy migrator to {0}", destinationPath), e); + } + } + + private String runMigrator(Path migratorPath, MigrationArguments migrationArguments) throws MigrationException + { + final String javaCommand = determineJavaCommand(); + + final List<String> command = new ArrayList<>(); + command.add(MessageFormat.format("\"{0}\"", javaCommand)); + command.add("-jar"); + command.add(migratorPath.toString()); + command.addAll(migrationArguments.getArguments()); + LOGGER.debug("Starting migration with command: {}", command); + + try + { + final ProcessBuilder processBuilder = new ProcessBuilder(command).redirectErrorStream(true); + final Process process = processBuilder.start(); + + final StringBuilder collectedStdout = new StringBuilder(); + try(BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) + { + while(true) + { + String line = in.readLine(); + if(line == null) + { + break; + } + + LOGGER.debug("[MIGRATOR] {}", line); + + collectedStdout.append(line); + collectedStdout.append("\n"); + } + } + + LOGGER.debug("Migration process finished"); + return collectedStdout.toString(); + } + catch(IOException e) + { + throw new MigrationException("Error during migration process", e); + } + } + + private String determineJavaCommand() throws MigrationException + { + final Optional<String> commandResultOptional = ProcessHandle.current().info().command(); + if(commandResultOptional.isEmpty()) + { + throw new MigrationException("Could not determine java executable"); + } + + return commandResultOptional.get().replace("\\", "/"); + } + + + public MigrationStatus getMigrationStatus() + { + return migrationStatus; + } + + public void setMigrationStatus(MigrationStatus migrationStatus) + { + this.migrationStatus = migrationStatus; + } +} diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index deac411684821ee2151fb2181f7a1fd989de4448..0fddc3c8902cf68367d87c8f282ded97ab0176f9 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -649,6 +649,7 @@ migration.settings.password=Passwort migration.settings.verification.password.description=Bitte gib dein aktuelles BudgetMaster Passwort ein, um den Migrationsprozess zu bestätigen.<br><span class="red-text bold">Die Migration kann nicht abgebrochen werden.</span><br>Deine bestehende BudgetMaster Datenbank wird weder geändert noch gelöscht. migration.settings.verification.password=Aktuelles BudgetMaster Passwort migration.settings.verification.password.wrong=Ungültiges Bestätigungspasswort +migration.status.not.running=Migration wurde nicht gestartet. migration.status.running=Migration läuft... -migration.status.finished=Migration fertiggestellt! +migration.status.success=Migration fertiggestellt! migration.status.error=Migration fehlgeschlagen! diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index 8166a4ba44d9156d1c4fbc778160a1c99e1c19bb..b672a014375f5075359aab3c3cd28b15f107b23a 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -648,7 +648,8 @@ migration.settings.password=Password migration.settings.verification.password.description=Please enter your current BudgetMaster password to confirm the migration process.<br><span class="red-text bold">The migration can not be canceled.</span><br>Your existing BudgetMaster database will neither be modified nor deleted. migration.settings.verification.password=Current BudgetMaster password migration.settings.verification.password.wrong=Invalid verification password +migration.status.not.running=Migration is not started. migration.status.running=Migration is running... -migration.status.finished=Migration finished! +migration.status.success=Migration finished! migration.status.error=Migration failed! diff --git a/BudgetMasterServer/src/main/resources/templates/migration/statusFragment.ftl b/BudgetMasterServer/src/main/resources/templates/migration/statusFragment.ftl index 80073e5b80ded45dff24a0ef1408d4285e9f0e04..83e8da527437683589b1774e63873486eb16341d 100644 --- a/BudgetMasterServer/src/main/resources/templates/migration/statusFragment.ftl +++ b/BudgetMasterServer/src/main/resources/templates/migration/statusFragment.ftl @@ -4,12 +4,11 @@ <div class="row"> <div class="col s12 m12 l8 offset-l2"> - <div class="headline-small">${locale.getString(statusTextKey)}</div> + <div class="headline-small">${locale.getString(status.getLocalizationKey())}</div> </div> </div> <div class="row"> <div class="col s12 m12 l8 offset-l2"> - Status: </div> </div> \ No newline at end of file diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/MigrationServiceTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/MigrationServiceTest.java index e6aa9c4ef30d7f6c37a4995368322fee97a94ad3..430c4b89d186ff3ae18274e3988064710d7c0152 100644 --- a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/MigrationServiceTest.java +++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/unit/MigrationServiceTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.Mockito; +import org.springframework.scheduling.TaskScheduler; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.io.IOException; @@ -48,6 +49,9 @@ class MigrationServiceTest @Mock private DatabaseConfigurationProperties databaseConfig; + @Mock + private TaskScheduler scheduler; + @TempDir public Path tempFolder; @@ -61,7 +65,7 @@ class MigrationServiceTest Mockito.when(settingsService.getSettings()).thenReturn(settings); - final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig); + final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig, scheduler); assertThat(migrationService.needToShowMigrationDialog("migration")).isFalse(); } @@ -76,7 +80,7 @@ class MigrationServiceTest Mockito.when(settingsService.getSettings()).thenReturn(settings); - final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig); + final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig, scheduler); assertThat(migrationService.needToShowMigrationDialog(Mappings.TRANSACTIONS)).isFalse(); } @@ -91,7 +95,7 @@ class MigrationServiceTest Mockito.when(settingsService.getSettings()).thenReturn(settings); Mockito.when(categoryRepository.findAllByTypeOrderByNameAsc(CategoryType.CUSTOM)).thenReturn(List.of(new Category("custom category", "ff0000", CategoryType.CUSTOM))); - final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig); + final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig, scheduler); assertThat(migrationService.needToShowMigrationDialog(Mappings.TRANSACTIONS)).isFalse(); } @@ -102,7 +106,7 @@ class MigrationServiceTest Mockito.when(settingsService.getSettings()).thenReturn(settings); - final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig); + final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig, scheduler); assertThat(migrationService.needToShowMigrationDialog(Mappings.TRANSACTIONS)).isFalse(); } @@ -116,7 +120,7 @@ class MigrationServiceTest Mockito.when(settingsService.getSettings()).thenReturn(settings); - final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig); + final MigrationService migrationService = new MigrationService(settingsService, tempFolder, accountRepository, categoryRepository, transactionRepository, templateRepository, databaseConfig, scheduler); assertThat(migrationService.needToShowMigrationDialog(Mappings.TRANSACTIONS)).isTrue(); } }