diff --git a/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java index 3b670b5..a515209 100644 --- a/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java +++ b/conda/src/main/java/com/github/gtache/autosubtitle/setup/conda/CondaSetupManager.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.setup.conda; import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupStatus; import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager; @@ -31,8 +32,8 @@ public class CondaSetupManager extends AbstractSetupManager { private final CondaSetupConfiguration configuration; @Inject - CondaSetupManager(final CondaSetupConfiguration configuration, final HttpClient httpClient) { - super(httpClient); + CondaSetupManager(final CondaSetupConfiguration configuration, final ProcessRunner processRunner, final HttpClient httpClient) { + super(processRunner, httpClient); this.configuration = requireNonNull(configuration); } @@ -71,31 +72,28 @@ public class CondaSetupManager extends AbstractSetupManager { switch (configuration.os()) { case OS.WINDOWS -> installWindows(); case OS.MAC, OS.LINUX -> installLinux(); + default -> throw new SetupException("Unsupported OS: " + configuration.os()); } } private void installLinux() throws SetupException { - try { - final var installerPath = configuration.condaInstallerPath(); - final var rootPath = configuration.condaRootPath(); - logger.info("Installing conda using {}", installerPath); - final var result = run(List.of("bash", installerPath.toString(), "-b", "-p", rootPath.toString()), Duration.ofMinutes(15)); - if (result.exitCode() == 0) { - logger.info("Installed conda to {}", rootPath); - } else { - throw new SetupException("Error installing conda: " + result); - } - } catch (final IOException e) { - throw new SetupException(e); - } + final var installerPath = configuration.condaInstallerPath(); + final var rootPath = configuration.condaRootPath(); + installArgs(List.of("bash", installerPath.toString(), "-b", "-p", rootPath.toString())); } private void installWindows() throws SetupException { + final var installerPath = configuration.condaInstallerPath(); + final var rootPath = configuration.condaRootPath(); + installArgs(List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath)); + } + + private void installArgs(final List args) throws SetupException { try { final var installerPath = configuration.condaInstallerPath(); final var rootPath = configuration.condaRootPath(); logger.info("Installing conda using {}", installerPath); - final var result = run(List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + rootPath.toString()), Duration.ofMinutes(15)); + final var result = processRunner().run(args, Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Installed conda to {}", rootPath); } else { @@ -111,6 +109,7 @@ public class CondaSetupManager extends AbstractSetupManager { case OS.WINDOWS -> downloadCondaWindows(); case OS.MAC -> downloadCondaMac(); case OS.LINUX -> downloadCondaLinux(); + default -> throw new SetupException("Unsupported OS: " + configuration.os()); } logger.info("Downloaded conda to {}", configuration.condaInstallerPath()); } @@ -154,7 +153,7 @@ public class CondaSetupManager extends AbstractSetupManager { @Override public void update() throws SetupException { try { - final var result = run(List.of(getCondaPath().toString(), "update", "-y", "conda"), Duration.ofMinutes(15)); + final var result = processRunner().run(List.of(getCondaPath().toString(), "update", "-y", "conda"), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Conda updated"); } else { @@ -169,7 +168,7 @@ public class CondaSetupManager extends AbstractSetupManager { final var args = Stream.concat(Stream.of(getCondaPath().toString(), "create", "-y", "-p", path.toString(), "python=" + pythonVersion), Arrays.stream(packages)).toList(); try { logger.info("Creating venv {}", path); - final var result = run(args, Duration.ofMinutes(15)); + final var result = processRunner().run(args, Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Created venv {}", path); } else { @@ -181,6 +180,7 @@ public class CondaSetupManager extends AbstractSetupManager { try (final var files = Files.find(sourceFolder, 1, (p, a) -> p.getFileName().toString().contains("libcrypto") || p.getFileName().toString().contains("libssl"))) { final var fileList = files.toList(); final var targetFolder = path.resolve("DLLs"); + Files.createDirectories(targetFolder); for (final var s : fileList) { Files.copy(s, targetFolder.resolve(s.getFileName())); } @@ -202,7 +202,7 @@ public class CondaSetupManager extends AbstractSetupManager { private boolean isSystemCondaInstalled() throws SetupException { try { - final var result = run(List.of(configuration.condaSystemPath().toString(), "--version"), Duration.ofSeconds(5)); + final var result = processRunner().run(List.of(configuration.condaSystemPath().toString(), "--version"), Duration.ofSeconds(5)); if (result.exitCode() == 0) { final var output = result.output().getFirst(); final var versionString = output.substring(output.indexOf(' ') + 1); @@ -212,7 +212,7 @@ public class CondaSetupManager extends AbstractSetupManager { final var minor = Integer.parseInt(version[1]); return major >= configuration.condaMinimumMajorVersion() && minor >= configuration.condaMinimumMinorVersion(); } else { - throw new SetupException("Unexpected python version: " + versionString); + throw new SetupException("Unexpected conda version: " + versionString); } } else { return false; diff --git a/conda/src/test/java/com/github/gtache/autosubtitle/modules/setup/conda/TestCondaSetupModule.java b/conda/src/test/java/com/github/gtache/autosubtitle/modules/setup/conda/TestCondaSetupModule.java new file mode 100644 index 0000000..64004c9 --- /dev/null +++ b/conda/src/test/java/com/github/gtache/autosubtitle/modules/setup/conda/TestCondaSetupModule.java @@ -0,0 +1,71 @@ +package com.github.gtache.autosubtitle.modules.setup.conda; + +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.setup.conda.CondaSetupConfiguration; +import org.junit.jupiter.api.Test; + +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestCondaSetupModule { + + @Test + void testCondaSetupConfiguration() { + final var rootPath = Paths.get("root"); + final var systemPath = Paths.get("system"); + final var bundledPath = Paths.get("bundled"); + final var condaMinimumMajorVersion = 24; + final var condaMinimumMinorVersion = 5; + final var condaInstallerPath = Paths.get("installer"); + final var os = OS.WINDOWS; + final var architecture = Architecture.X86_64; + final var expected = new CondaSetupConfiguration(rootPath, systemPath, bundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture); + assertEquals(expected, CondaSetupModule.providesCondaSetupConfiguration(systemPath, bundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, rootPath, os, architecture)); + } + + @Test + void testProvidesCondaSystemPath() { + assertEquals(Paths.get("conda.bat"), CondaSetupModule.providesCondaSystemPath(OS.WINDOWS)); + assertEquals(Paths.get("conda"), CondaSetupModule.providesCondaSystemPath(OS.LINUX)); + assertEquals(Paths.get("conda"), CondaSetupModule.providesCondaSystemPath(OS.MAC)); + } + + @Test + void testCondaBundledPath() { + final var root = Paths.get("root"); + final var expectedWindows = root.resolve("condabin").resolve(Paths.get("conda.bat")); + final var expectedOther = root.resolve("condabin").resolve(Paths.get("conda")); + assertEquals(expectedWindows, CondaSetupModule.providesCondaBundledPath(root, OS.WINDOWS)); + assertEquals(expectedOther, CondaSetupModule.providesCondaBundledPath(root, OS.MAC)); + assertEquals(expectedOther, CondaSetupModule.providesCondaBundledPath(root, OS.LINUX)); + } + + @Test + void testCondaMinimumMajorVersion() { + assertEquals(24, CondaSetupModule.providesCondaMinimumMajorVersion()); + } + + @Test + void testCondaMinimumMinorVersion() { + assertEquals(5, CondaSetupModule.providesCondaMinimumMinorVersion()); + } + + @Test + void testCondaRootPath() { + final var root = Paths.get("root"); + final var expected = root.resolve("miniconda3"); + assertEquals(expected, CondaSetupModule.providesCondaRootPath(root)); + } + + @Test + void testCondaInstallerPath() { + final var root = Paths.get("root"); + final var expectedWindows = root.resolve("conda-install.exe"); + final var expectedOther = root.resolve("conda-install.sh"); + assertEquals(expectedWindows, CondaSetupModule.providesCondaInstallerPath(root, OS.WINDOWS)); + assertEquals(expectedOther, CondaSetupModule.providesCondaInstallerPath(root, OS.MAC)); + assertEquals(expectedOther, CondaSetupModule.providesCondaInstallerPath(root, OS.LINUX)); + } +} diff --git a/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupConfiguration.java b/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupConfiguration.java new file mode 100644 index 0000000..99a87bb --- /dev/null +++ b/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupConfiguration.java @@ -0,0 +1,65 @@ +package com.github.gtache.autosubtitle.setup.conda; + +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.nio.file.Path; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class TestCondaSetupConfiguration { + + private final Path condaRootPath; + private final Path condaSystemPath; + private final Path condaBundledPath; + private final int condaMinimumMajorVersion; + private final int condaMinimumMinorVersion; + private final Path condaInstallerPath; + private final OS os; + private final Architecture architecture; + + TestCondaSetupConfiguration(@Mock final Path condaRootPath, @Mock final Path condaSystemPath, + @Mock final Path condaBundledPath, @Mock final Path condaInstallerPath, + @Mock final OS os, @Mock final Architecture architecture) { + this.condaRootPath = Objects.requireNonNull(condaRootPath); + this.condaSystemPath = Objects.requireNonNull(condaSystemPath); + this.condaBundledPath = Objects.requireNonNull(condaBundledPath); + this.condaMinimumMajorVersion = 3; + this.condaMinimumMinorVersion = 4; + this.condaInstallerPath = Objects.requireNonNull(condaInstallerPath); + this.os = Objects.requireNonNull(os); + this.architecture = Objects.requireNonNull(architecture); + } + + @Test + void testCondaSetupConfiguration() { + final var configuration = new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture); + assertEquals(condaRootPath, configuration.condaRootPath()); + assertEquals(condaSystemPath, configuration.condaSystemPath()); + assertEquals(condaBundledPath, configuration.condaBundledPath()); + assertEquals(condaMinimumMajorVersion, configuration.condaMinimumMajorVersion()); + assertEquals(condaMinimumMinorVersion, configuration.condaMinimumMinorVersion()); + assertEquals(condaInstallerPath, configuration.condaInstallerPath()); + assertEquals(os, configuration.os()); + assertEquals(architecture, configuration.architecture()); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(null, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(condaRootPath, null, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, null, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, architecture)); + assertThrows(IllegalArgumentException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, 0, condaMinimumMinorVersion, condaInstallerPath, os, architecture)); + assertThrows(IllegalArgumentException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, -1, condaInstallerPath, os, architecture)); + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, null, os, architecture)); + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, null, architecture)); + assertThrows(NullPointerException.class, () -> new CondaSetupConfiguration(condaRootPath, condaSystemPath, condaBundledPath, condaMinimumMajorVersion, condaMinimumMinorVersion, condaInstallerPath, os, null)); + } +} diff --git a/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupManager.java b/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupManager.java new file mode 100644 index 0000000..3d01b4f --- /dev/null +++ b/conda/src/test/java/com/github/gtache/autosubtitle/setup/conda/TestCondaSetupManager.java @@ -0,0 +1,513 @@ +package com.github.gtache.autosubtitle.setup.conda; + +import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessResult; +import com.github.gtache.autosubtitle.process.ProcessRunner; +import com.github.gtache.autosubtitle.setup.SetupException; +import com.github.gtache.autosubtitle.setup.SetupStatus; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.List; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TestCondaSetupManager { + private static final String PYTHON_VERSION = "3.3"; + private static final String[] PACKAGES = {"p1", "p2"}; + + + private final CondaSetupConfiguration configuration; + private final ProcessRunner processRunner; + private final HttpClient httpClient; + private final CondaSetupManager condaSetupManager; + private final ProcessResult systemProcessResult; + private final Path systemPath; + private final HttpResponse response; + + TestCondaSetupManager(@Mock final CondaSetupConfiguration configuration, @Mock final ProcessRunner processRunner, + @Mock final HttpClient httpClient, @Mock final ProcessResult systemProcessResult, @Mock final HttpResponse response) throws IOException, InterruptedException { + this.configuration = requireNonNull(configuration); + this.processRunner = requireNonNull(processRunner); + this.httpClient = requireNonNull(httpClient); + this.condaSetupManager = new CondaSetupManager(configuration, processRunner, httpClient); + this.systemProcessResult = requireNonNull(systemProcessResult); + this.systemPath = Paths.get("system"); + this.response = requireNonNull(response); + when(response.statusCode()).thenReturn(200); + when(configuration.condaSystemPath()).thenReturn(systemPath); + when(systemProcessResult.output()).thenReturn(List.of("conda 99.99.99")); + when(systemProcessResult.exitCode()).thenReturn(0); + when(processRunner.run(List.of(systemPath.toString(), "--version"), Duration.ofSeconds(5))).thenReturn(systemProcessResult); + when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(response); + } + + @Test + void testName() { + assertEquals("conda", condaSetupManager.name()); + } + + @Test + void testGetStatusIsSystemInstalled() throws SetupException { + assertEquals(SetupStatus.SYSTEM_INSTALLED, condaSetupManager.getStatus()); + } + + @Test + void testGetStatusIsBundledInstalled(@TempDir final Path tempDir) throws SetupException, IOException { + final var path = tempDir.resolve("conda"); + Files.createFile(path); + when(configuration.condaBundledPath()).thenReturn(path); + when(systemProcessResult.exitCode()).thenReturn(1); + + assertEquals(SetupStatus.BUNDLE_INSTALLED, condaSetupManager.getStatus()); + } + + @Test + void testGetStatusNotInstalled() throws SetupException { + final var path = Paths.get("conda"); + when(configuration.condaBundledPath()).thenReturn(path); + when(systemProcessResult.exitCode()).thenReturn(1); + assertEquals(SetupStatus.NOT_INSTALLED, condaSetupManager.getStatus()); + } + + @Test + void testInstallIsInstalledSystem() { + assertDoesNotThrow(condaSetupManager::install); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallIsInstalledBundled(@TempDir final Path tempDir) throws IOException { + final var path = tempDir.resolve("conda"); + Files.createFile(path); + when(configuration.condaBundledPath()).thenReturn(path); + when(systemProcessResult.exitCode()).thenReturn(1); + assertDoesNotThrow(condaSetupManager::install); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallDownloadWindows(@TempDir final Path tempDir) throws IOException, InterruptedException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.WINDOWS); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + tempDir); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class); + verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class)); + final var request = requestCapture.getValue(); + assertEquals("https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe", request.uri().toString()); + verify(processRunner).run(args, Duration.ofMinutes(15)); + } + + @Test + void testInstallDownloadLinuxAMD64(@TempDir final Path tempDir) throws IOException, InterruptedException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(configuration.architecture()).thenReturn(Architecture.AMD64); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class); + verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class)); + final var request = requestCapture.getValue(); + assertEquals("https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh", request.uri().toString()); + verify(processRunner).run(args, Duration.ofMinutes(15)); + } + + @Test + void testInstallDownloadLinuxARM64(@TempDir final Path tempDir) throws IOException, InterruptedException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(configuration.architecture()).thenReturn(Architecture.ARM64); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class); + verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class)); + final var request = requestCapture.getValue(); + assertEquals("https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh", request.uri().toString()); + verify(processRunner).run(args, Duration.ofMinutes(15)); + } + + @Test + void testInstallDownloadLinuxUnsupported(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(configuration.architecture()).thenReturn(Architecture.ARM32); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + assertThrows(SetupException.class, condaSetupManager::install); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallDownloadMacAMD64(@TempDir final Path tempDir) throws IOException, InterruptedException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.MAC); + when(configuration.architecture()).thenReturn(Architecture.AMD64); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class); + verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class)); + final var request = requestCapture.getValue(); + assertEquals("https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh", request.uri().toString()); + verify(processRunner).run(args, Duration.ofMinutes(15)); + } + + @Test + void testInstallDownloadMacARM64(@TempDir final Path tempDir) throws IOException, InterruptedException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.MAC); + when(configuration.architecture()).thenReturn(Architecture.ARM64); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class); + verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class)); + final var request = requestCapture.getValue(); + assertEquals("https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh", request.uri().toString()); + verify(processRunner).run(args, Duration.ofMinutes(15)); + } + + @Test + void testInstallDownloadMacUnsupported(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.MAC); + when(configuration.architecture()).thenReturn(Architecture.ARM32); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + assertThrows(SetupException.class, condaSetupManager::install); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallDownloadUnsupported(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.UNKNOWN); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + assertThrows(SetupException.class, condaSetupManager::install); + verifyNoInteractions(httpClient); + } + + + @Test + void testInstallInstallerWindows(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + Files.createFile(installerPath); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.WINDOWS); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of(installerPath.toString(), "/InstallationType=JustMe", "/RegisterPython=0", "/S", "/D=" + tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + verify(processRunner).run(args, Duration.ofMinutes(15)); + verifyNoInteractions(httpClient); + } + + + @Test + void testInstallInstallerLinux(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + Files.createFile(installerPath); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertDoesNotThrow(condaSetupManager::install); + verify(processRunner).run(args, Duration.ofMinutes(15)); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallInstallerLinuxIOException(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + Files.createFile(installerPath); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + when(processRunner.run(args, Duration.ofMinutes(15))).thenThrow(IOException.class); + + assertThrows(SetupException.class, condaSetupManager::install); + verify(processRunner).run(args, Duration.ofMinutes(15)); + verifyNoInteractions(httpClient); + } + + @Test + void testInstallInstallerLinuxBadExitCode(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + Files.createFile(installerPath); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.LINUX); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaRootPath()).thenReturn(tempDir); + + final var args = List.of("bash", installerPath.toString(), "-b", "-p", tempDir.toString()); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(1); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + + assertThrows(SetupException.class, condaSetupManager::install); + verify(processRunner).run(args, Duration.ofMinutes(15)); + verifyNoInteractions(httpClient); + } + + + @Test + void testInstallInstallerUnknown(@TempDir final Path tempDir) throws IOException { + final var installerPath = tempDir.resolve("conda"); + Files.createFile(installerPath); + when(configuration.condaInstallerPath()).thenReturn(installerPath); + when(configuration.condaBundledPath()).thenReturn(tempDir); + when(configuration.os()).thenReturn(OS.UNKNOWN); + when(systemProcessResult.exitCode()).thenReturn(1); + + assertThrows(SetupException.class, condaSetupManager::install); + verifyNoInteractions(httpClient); + + } + + @Test + void testUninstall(@TempDir final Path tempDir) throws IOException { + when(configuration.condaRootPath()).thenReturn(tempDir); + Files.createDirectories(tempDir.resolve("sub")); + Files.createFile(tempDir.resolve("sub").resolve("conda")); + Files.createFile(tempDir.resolve("python")); + assertDoesNotThrow(condaSetupManager::uninstall); + assertFalse(Files.isDirectory(tempDir)); + } + + @Test + void testUpdateBundled() throws IOException { + final var path = Path.of("test"); + final var args = List.of(path.toString(), "update", "-y", "conda"); + final var duration = Duration.ofMinutes(15); + when(configuration.condaBundledPath()).thenReturn(path); + when(systemProcessResult.exitCode()).thenReturn(1); + final var processResult = mock(ProcessResult.class); + when(processResult.exitCode()).thenReturn(0); + when(processRunner.run(args, duration)).thenReturn(processResult); + assertDoesNotThrow(condaSetupManager::update); + verify(processRunner).run(args, duration); + } + + @Test + void testUpdateSystemInstalled() throws IOException { + final var args = List.of(systemPath.toString(), "update", "-y", "conda"); + final var duration = Duration.ofMinutes(15); + final var processResult = mock(ProcessResult.class); + when(processResult.exitCode()).thenReturn(0); + when(processRunner.run(args, duration)).thenReturn(processResult); + assertDoesNotThrow(condaSetupManager::update); + verify(processRunner).run(args, duration); + verify(processRunner).run(List.of(systemPath.toString(), "--version"), Duration.ofSeconds(5)); + } + + @Test + void testUpdateSystemIOException() throws IOException { + when(processRunner.run(List.of(systemPath.toString(), "--version"), Duration.ofSeconds(5))).thenThrow(IOException.class); + assertThrows(SetupException.class, condaSetupManager::update); + } + + @Test + void testUpdateSystemBadVersion() { + final var path = Path.of("test"); + when(systemProcessResult.output()).thenReturn(List.of("conda 9")); + when(configuration.condaBundledPath()).thenReturn(path); + when(configuration.condaSystemPath()).thenReturn(systemPath); + assertThrows(SetupException.class, condaSetupManager::update); + } + + + @Test + void testUpdateBundleBadResultCode() throws IOException { + final var path = Path.of("test"); + final var args = List.of(path.toString(), "update", "-y", "conda"); + final var duration = Duration.ofMinutes(15); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaBundledPath()).thenReturn(path); + when(processRunner.run(args, duration)).thenReturn(systemProcessResult); + when(systemProcessResult.exitCode()).thenReturn(1); + assertThrows(SetupException.class, condaSetupManager::update); + verify(processRunner).run(args, duration); + } + + @Test + void testUpdateBundleIOException() throws IOException { + final var path = Path.of("test"); + final var args = List.of(path.toString(), "update", "-y", "conda"); + final var duration = Duration.ofMinutes(15); + when(systemProcessResult.exitCode()).thenReturn(1); + when(configuration.condaBundledPath()).thenReturn(path); + when(processRunner.run(args, duration)).thenThrow(IOException.class); + assertThrows(SetupException.class, condaSetupManager::update); + verify(processRunner).run(args, duration); + } + + @Test + void testCreateVenv() throws IOException { + final var path = Paths.get("test"); + final var args = List.of(systemPath.toString(), "create", "-y", "-p", path.toString(), "python=" + PYTHON_VERSION, "p1", "p2"); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(configuration.os()).thenReturn(OS.LINUX); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + assertDoesNotThrow(() -> condaSetupManager.createVenv(path, PYTHON_VERSION, PACKAGES)); + } + + @Test + void testCreateVenvWindows(@TempDir final Path tempDir) throws IOException { + final var path = tempDir.resolve("test"); + final var bin = path.resolve("Library").resolve("bin"); + Files.createDirectories(bin); + final var crypto1 = bin.resolve("libcrypto-1.1.dll"); + final var crypto2 = bin.resolve("libcrypto-2.sdf"); + final var ssl1 = bin.resolve("libssl-1.1.dll"); + final var ssl2 = bin.resolve("libssl-2.sdf"); + final var other = bin.resolve("other.dll"); + final var dlls = path.resolve("DLLs"); + for (final var path1 : List.of(crypto1, crypto2, ssl1, ssl2, other)) { + Files.createFile(path1); + } + final var args = List.of(systemPath.toString(), "create", "-y", "-p", path.toString(), "python=" + PYTHON_VERSION, "p1", "p2"); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(0); + when(configuration.os()).thenReturn(OS.WINDOWS); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + assertDoesNotThrow(() -> condaSetupManager.createVenv(path, PYTHON_VERSION, PACKAGES)); + for (final var path1 : List.of(crypto1, crypto2, ssl1, ssl2)) { + assertTrue(Files.exists(dlls.resolve(path1.getFileName()))); + } + assertFalse(Files.exists(dlls.resolve(other.getFileName()))); + } + + @Test + void testCreateVenvIOException() throws IOException { + final var path = Paths.get("test"); + final var args = List.of(systemPath.toString(), "create", "-y", "-p", path.toString(), "python=" + PYTHON_VERSION, "p1", "p2"); + when(processRunner.run(args, Duration.ofMinutes(15))).thenThrow(IOException.class); + assertThrows(SetupException.class, () -> condaSetupManager.createVenv(path, PYTHON_VERSION, PACKAGES)); + } + + @Test + void testCreateVenvBadResultCode() throws IOException { + final var path = Paths.get("test"); + final var args = List.of(systemPath.toString(), "create", "-y", "-p", path.toString(), "python=" + PYTHON_VERSION, "p1", "p2"); + final var result = mock(ProcessResult.class); + when(result.exitCode()).thenReturn(1); + when(processRunner.run(args, Duration.ofMinutes(15))).thenReturn(result); + assertThrows(SetupException.class, () -> condaSetupManager.createVenv(path, PYTHON_VERSION, PACKAGES)); + } + + @Test + void testVenvExists(@TempDir final Path tempDirectory) throws IOException { + assertFalse(condaSetupManager.venvExists(tempDirectory)); + Files.createDirectories(tempDirectory.resolve("bin")); + + when(configuration.os()).thenReturn(OS.WINDOWS); + Files.createFile(tempDirectory.resolve("bin").resolve("python.exe")); + assertTrue(condaSetupManager.venvExists(tempDirectory)); + + when(configuration.os()).thenReturn(OS.LINUX); + assertFalse(condaSetupManager.venvExists(tempDirectory)); + + Files.createFile(tempDirectory.resolve("bin").resolve("python")); + assertTrue(condaSetupManager.venvExists(tempDirectory)); + + when(configuration.os()).thenReturn(OS.MAC); + assertTrue(condaSetupManager.venvExists(tempDirectory)); + } + + @Test + void testIllegal() { + assertThrows(NullPointerException.class, () -> new CondaSetupManager(null, processRunner, httpClient)); + assertThrows(NullPointerException.class, () -> new CondaSetupManager(configuration, null, httpClient)); + assertThrows(NullPointerException.class, () -> new CondaSetupManager(configuration, processRunner, null)); + } + +} diff --git a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java index 95390ce..b5589c2 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/modules/impl/CoreModule.java @@ -6,6 +6,9 @@ import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.modules.archive.impl.ArchiveModule; import com.github.gtache.autosubtitle.modules.setup.impl.SetupModule; import com.github.gtache.autosubtitle.modules.subtitle.impl.SubtitleModule; +import com.github.gtache.autosubtitle.process.ProcessRunner; +import com.github.gtache.autosubtitle.process.impl.ProcessRunnerImpl; +import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -22,6 +25,9 @@ public abstract class CoreModule { } + @Binds + abstract ProcessRunner bindsProcessRunner(final ProcessRunnerImpl processRunner); + @Provides static OS providesOS() { final var os = OS.getOS(); diff --git a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessRunnerImpl.java similarity index 55% rename from core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java rename to core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessRunnerImpl.java index 870c560..a78e446 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/process/impl/AbstractProcessRunner.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/process/impl/ProcessRunnerImpl.java @@ -6,16 +6,22 @@ import com.github.gtache.autosubtitle.process.ProcessRunner; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.inject.Inject; import java.io.IOException; import java.time.Duration; import java.util.List; /** - * Base implementation of {@link ProcessRunner} + * Implementation of {@link ProcessRunner} */ -public abstract class AbstractProcessRunner implements ProcessRunner { +public class ProcessRunnerImpl implements ProcessRunner { - private static final Logger logger = LogManager.getLogger(AbstractProcessRunner.class); + private static final Logger logger = LogManager.getLogger(ProcessRunnerImpl.class); + + @Inject + ProcessRunnerImpl() { + + } @Override public ProcessResult run(final List args, final Duration duration) throws IOException { @@ -37,22 +43,5 @@ public abstract class AbstractProcessRunner implements ProcessRunner { return new ProcessListenerImpl(process); } - /** - * Runs a process and writes the output to the log - * - * @param args the command - * @param duration The maximum duration to wait for - * @return the result - * @throws IOException if an error occurs - */ - protected ProcessResult runListen(final List args, final Duration duration) throws IOException { - final var listener = startListen(args); - var line = listener.readLine(); - final var processName = args.getFirst(); - while (line != null) { - logger.info("[{}]: {}", processName, line); - line = listener.readLine(); - } - return listener.join(duration); - } + } diff --git a/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java b/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java index 3d26e26..0a75511 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/setup/impl/AbstractSetupManager.java @@ -1,6 +1,6 @@ package com.github.gtache.autosubtitle.setup.impl; -import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupAction; import com.github.gtache.autosubtitle.setup.SetupEvent; import com.github.gtache.autosubtitle.setup.SetupException; @@ -28,27 +28,23 @@ import static java.util.Objects.requireNonNull; /** * Base class for all {@link SetupManager} implementations */ -public abstract class AbstractSetupManager extends AbstractProcessRunner implements SetupManager { +public abstract class AbstractSetupManager implements SetupManager { private static final Logger logger = LogManager.getLogger(AbstractSetupManager.class); private final Set listeners; private final HttpClient httpClient; + private final ProcessRunner processRunner; /** - * Instantiates the manager with a default client - */ - protected AbstractSetupManager() { - this(HttpClient.newHttpClient()); - } - - /** - * Instantiates the manager with the given client + * Instantiates the manager with the given runner and client * - * @param httpClient The HTTP client to use + * @param processRunner The process runner + * @param httpClient The HTTP client to use */ - protected AbstractSetupManager(final HttpClient httpClient) { - this.listeners = new HashSet<>(); + protected AbstractSetupManager(final ProcessRunner processRunner, final HttpClient httpClient) { + this.processRunner = requireNonNull(processRunner); this.httpClient = requireNonNull(httpClient); + this.listeners = new HashSet<>(); } @Override @@ -100,6 +96,13 @@ public abstract class AbstractSetupManager extends AbstractProcessRunner impleme } } + /** + * @return The process runner used by this manager + */ + protected ProcessRunner processRunner() { + return processRunner; + } + /** * @return Retrieves the setup status * @throws SetupException if an error occurred diff --git a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestProcessRunnerImpl.java similarity index 66% rename from core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java rename to core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestProcessRunnerImpl.java index f9a0405..f819404 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestAbstractProcessRunner.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/process/impl/TestProcessRunnerImpl.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.process.impl; import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessRunner; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -10,25 +11,25 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -class TestAbstractProcessRunner { +class TestProcessRunnerImpl { private static final List ARGS = OS.getOS() == OS.WINDOWS ? List.of("powershell.exe", "-Command", "\"echo '1\n2\n3'\"") : List.of("echo", "1\n2\n3"); - private final DummyProcessRunner dummyProcessRunner; + private final ProcessRunner runner; - TestAbstractProcessRunner() { - this.dummyProcessRunner = new DummyProcessRunner(); + TestProcessRunnerImpl() { + this.runner = new ProcessRunnerImpl(); } @Test void testRun() throws IOException { final var expected = new ProcessResultImpl(0, List.of("1", "2", "3")); - final var actual = dummyProcessRunner.run(ARGS, Duration.ofSeconds(5)); + final var actual = runner.run(ARGS, Duration.ofSeconds(5)); assertEquals(expected, actual); } @Test void testStart() throws IOException, InterruptedException { - final var process = dummyProcessRunner.start(ARGS); + final var process = runner.start(ARGS); process.waitFor(); assertEquals(0, process.exitValue()); final var read = new String(process.getInputStream().readAllBytes()); @@ -38,7 +39,7 @@ class TestAbstractProcessRunner { @Test void testStartListen() throws IOException { - final var listener = dummyProcessRunner.startListen(ARGS); + final var listener = runner.startListen(ARGS); assertEquals("1", listener.readLine()); assertEquals("2", listener.readLine()); assertEquals("3", listener.readLine()); @@ -47,15 +48,4 @@ class TestAbstractProcessRunner { assertEquals(0, result.exitCode()); assertEquals(List.of("1", "2", "3"), result.output()); } - - @Test - void testRunListen() throws IOException { - final var result = dummyProcessRunner.runListen(ARGS, Duration.ofSeconds(5)); - assertEquals(0, result.exitCode()); - assertEquals(List.of("1", "2", "3"), result.output()); - } - - private static final class DummyProcessRunner extends AbstractProcessRunner { - - } } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/setup/impl/TestAbstractSetupManager.java b/core/src/test/java/com/github/gtache/autosubtitle/setup/impl/TestAbstractSetupManager.java index af5d8bd..49dafa9 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/setup/impl/TestAbstractSetupManager.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/setup/impl/TestAbstractSetupManager.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.setup.impl; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupAction; import com.github.gtache.autosubtitle.setup.SetupEvent; import com.github.gtache.autosubtitle.setup.SetupException; @@ -29,18 +30,21 @@ class TestAbstractSetupManager { private final SetupListener listener2; private final SetupEvent event; private final SetupAction setupAction; + private final ProcessRunner processRunner; private final HttpClient httpClient; TestAbstractSetupManager(@Mock final SetupListener listener1, @Mock final SetupListener listener2, @Mock final SetupEvent event, @Mock final SetupAction setupAction, + @Mock final ProcessRunner processRunner, @Mock final HttpClient httpClient) { - this.manager = spy(new DummySetupManager(httpClient)); + this.manager = spy(new DummySetupManager(processRunner, httpClient)); this.listener1 = requireNonNull(listener1); this.listener2 = requireNonNull(listener2); this.event = requireNonNull(event); this.setupAction = requireNonNull(setupAction); + this.processRunner = requireNonNull(processRunner); this.httpClient = requireNonNull(httpClient); } @@ -223,13 +227,13 @@ class TestAbstractSetupManager { @Test void testIllegal() { - assertThrows(NullPointerException.class, () -> new DummySetupManager(null)); + assertThrows(NullPointerException.class, () -> new DummySetupManager(null, null)); } private static final class DummySetupManager extends AbstractSetupManager { - private DummySetupManager(final HttpClient httpClient) { - super(httpClient); + private DummySetupManager(final ProcessRunner processRunner, final HttpClient httpClient) { + super(processRunner, httpClient); } @Override diff --git a/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java b/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java index c00ba40..f2129c3 100644 --- a/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java +++ b/deepl/src/main/java/com/github/gtache/autosubtitle/setup/deepl/DeepLSetupManager.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.setup.deepl; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; import com.github.gtache.autosubtitle.setup.SetupStatus; @@ -7,6 +8,7 @@ import com.github.gtache.autosubtitle.setup.SetupUserBridge; import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager; import javax.inject.Inject; +import java.net.http.HttpClient; import java.util.Objects; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; @@ -22,7 +24,9 @@ public class DeepLSetupManager extends AbstractSetupManager { private final Preferences preferences; @Inject - DeepLSetupManager(final SetupUserBridge userBridge, final Preferences preferences) { + DeepLSetupManager(final SetupUserBridge userBridge, final Preferences preferences, final ProcessRunner processRunner, + final HttpClient httpClient) { + super(processRunner, httpClient); this.userBridge = Objects.requireNonNull(userBridge); this.preferences = Objects.requireNonNull(preferences); } diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java index 2bb08d5..bcea11f 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFmpegVideoConverter.java @@ -11,10 +11,12 @@ import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath; -import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.inject.Inject; import java.io.IOException; @@ -34,21 +36,24 @@ import static java.util.Objects.requireNonNull; /** * FFmpeg implementation of {@link VideoConverter} */ -public class FFmpegVideoConverter extends AbstractProcessRunner implements VideoConverter { - +public class FFmpegVideoConverter implements VideoConverter { + private static final Logger logger = LogManager.getLogger(FFmpegVideoConverter.class); private static final String TEMP_FILE_PREFIX = "autosubtitle"; private final Path bundledPath; private final Path systemPath; private final SubtitleConverterProvider converterProvider; private final Preferences preferences; + private final ProcessRunner processRunner; @Inject FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath, - final SubtitleConverterProvider converterProvider, final Preferences preferences) { + final SubtitleConverterProvider converterProvider, final Preferences preferences, + final ProcessRunner processRunner) { this.bundledPath = requireNonNull(bundledPath); this.systemPath = requireNonNull(systemPath); this.converterProvider = requireNonNull(converterProvider); this.preferences = requireNonNull(preferences); + this.processRunner = requireNonNull(processRunner); } @Override @@ -197,4 +202,15 @@ public class FFmpegVideoConverter extends AbstractProcessRunner implements Video private String getFFmpegPath() { return Files.isRegularFile(bundledPath) ? bundledPath.toString() : systemPath.toString(); } + + private void runListen(final List args, final Duration duration) throws IOException { + final var listener = processRunner.startListen(args); + var line = listener.readLine(); + final var processName = args.getFirst(); + while (line != null) { + logger.info("[{}]: {}", processName, line); + line = listener.readLine(); + } + listener.join(duration); + } } diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java index 97d965e..0b0bb40 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/ffmpeg/FFprobeVideoLoader.java @@ -6,7 +6,7 @@ import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.VideoInfoImpl; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeBundledPath; import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFprobeSystemPath; -import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; +import com.github.gtache.autosubtitle.process.ProcessRunner; import javax.inject.Inject; import java.io.IOException; @@ -20,20 +20,23 @@ import static java.util.Objects.requireNonNull; /** * FFprobe implementation of {@link VideoLoader} */ -public class FFprobeVideoLoader extends AbstractProcessRunner implements VideoLoader { +public class FFprobeVideoLoader implements VideoLoader { private final Path bundledPath; private final Path systemPath; + private final ProcessRunner processRunner; @Inject - FFprobeVideoLoader(@FFprobeBundledPath final Path bundledPath, @FFprobeSystemPath final Path systemPath) { + FFprobeVideoLoader(@FFprobeBundledPath final Path bundledPath, @FFprobeSystemPath final Path systemPath, + final ProcessRunner processRunner) { this.bundledPath = requireNonNull(bundledPath); this.systemPath = requireNonNull(systemPath); + this.processRunner = requireNonNull(processRunner); } @Override public Video loadVideo(final Path path) throws IOException { - final var result = run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5)); + final var result = processRunner.run(List.of(getFFprobePath(), "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height,duration", "-of", "csv=p=0", path.toString()), Duration.ofSeconds(5)); final var resolution = result.output().getLast(); final var split = resolution.split(","); final var width = Integer.parseInt(split[0]); diff --git a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java index ecc0ad3..3929623 100644 --- a/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java +++ b/ffmpeg/src/main/java/com/github/gtache/autosubtitle/setup/ffmpeg/FFmpegSetupManager.java @@ -2,6 +2,7 @@ package com.github.gtache.autosubtitle.setup.ffmpeg; import com.github.gtache.autosubtitle.archive.ArchiverProvider; import com.github.gtache.autosubtitle.impl.Architecture; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; import com.github.gtache.autosubtitle.setup.SetupStatus; @@ -32,8 +33,8 @@ public class FFmpegSetupManager extends AbstractSetupManager { @Inject FFmpegSetupManager(final FFmpegSetupConfiguration configuration, final ArchiverProvider archiverProvider, - final HttpClient httpClient) { - super(httpClient); + final ProcessRunner processRunner, final HttpClient httpClient) { + super(processRunner, httpClient); this.configuration = requireNonNull(configuration); this.archiverProvider = requireNonNull(archiverProvider); } @@ -190,7 +191,7 @@ public class FFmpegSetupManager extends AbstractSetupManager { } private boolean checkSystemFFmpeg() throws IOException { - final var result = run(List.of(configuration.systemFFmpegPath().toString(), "-version"), Duration.ofSeconds(5)); + final var result = processRunner().run(List.of(configuration.systemFFmpegPath().toString(), "-version"), Duration.ofSeconds(5)); return result.exitCode() == 0; } diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java index 16f9db3..56b65fa 100644 --- a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoConverter.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.ffmpeg; import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.VideoInfo; import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.when; class TestFFmpegVideoConverter { private final FFmpegVideoConverter converter; + private final ProcessRunner runner; private final SubtitleConverter subtitleConverter; private final SubtitleConverterProvider subtitleConverterProvider; private final Video video; @@ -33,11 +35,12 @@ class TestFFmpegVideoConverter { private final SubtitleCollection collection; private final Preferences preferences; - TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, + TestFFmpegVideoConverter(@Mock final SubtitleConverter subtitleConverter, @Mock final ProcessRunner runner, @Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, @Mock final VideoInfo videoInfo, @Mock final SubtitleCollection collection, @Mock final Preferences preferences) throws IOException { final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffmpeg.exe" : "fake-ffmpeg.sh"; this.video = Objects.requireNonNull(video); + this.runner = Objects.requireNonNull(runner); this.videoInfo = Objects.requireNonNull(videoInfo); when(video.info()).thenReturn(videoInfo); this.tmpFile = Files.createTempFile("fake-ffmpeg", resource.substring(resource.lastIndexOf('.'))); @@ -48,7 +51,7 @@ class TestFFmpegVideoConverter { this.subtitleConverter = Objects.requireNonNull(subtitleConverter); this.subtitleConverterProvider = Objects.requireNonNull(subtitleConverterProvider); this.preferences = Objects.requireNonNull(preferences); - this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences); + this.converter = new FFmpegVideoConverter(tmpFile, tmpFile, subtitleConverterProvider, preferences, runner); this.collection = Objects.requireNonNull(collection); } diff --git a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java index faef35e..d81de64 100644 --- a/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java +++ b/ffmpeg/src/test/java/com/github/gtache/autosubtitle/ffmpeg/TestFFmpegVideoLoader.java @@ -3,25 +3,32 @@ package com.github.gtache.autosubtitle.ffmpeg; import com.github.gtache.autosubtitle.impl.FileVideoImpl; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.impl.VideoInfoImpl; +import com.github.gtache.autosubtitle.process.ProcessRunner; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; +@ExtendWith(MockitoExtension.class) class TestFFmpegVideoLoader { private final FFprobeVideoLoader loader; + private final ProcessRunner runner; private final Path tmpFile; private final Path outputPath; - TestFFmpegVideoLoader() throws IOException { + TestFFmpegVideoLoader(@Mock final ProcessRunner runner) throws IOException { final var output = (OS.getOS() == OS.WINDOWS ? System.getProperty("java.io.tmpdir") : "/tmp"); final var resource = OS.getOS() == OS.WINDOWS ? "fake-ffprobe.exe" : "fake-ffprobe.sh"; this.tmpFile = Files.createTempFile("fake-ffprobe", resource.substring(resource.lastIndexOf('.'))); @@ -29,7 +36,8 @@ class TestFFmpegVideoLoader { Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING); } this.outputPath = Path.of(output, "test-ffprobe-output.txt"); - this.loader = new FFprobeVideoLoader(tmpFile, tmpFile); + this.runner = Objects.requireNonNull(runner); + this.loader = new FFprobeVideoLoader(tmpFile, tmpFile, runner); } @AfterEach diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties index dc37480..770d055 100644 --- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle.properties @@ -1,3 +1,4 @@ +subtitles.add.prompt.label=Enter text here... subtitles.button.load.label=Load subtitles... subtitles.button.reset.label=Reset subtitles subtitles.button.subtitles.save.label=Save subtitles... diff --git a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties index b3369b1..9ea4315 100644 --- a/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties +++ b/gui/core/src/main/resources/com/github/gtache/autosubtitle/gui/impl/SubtitlesBundle_fr.properties @@ -1,3 +1,4 @@ +subtitles.add.prompt.label=Entrez le texte ici... subtitles.button.load.label=Charger des sous-titres... subtitles.button.reset.label=R\u00E9initialiser les sous-titres subtitles.button.subtitles.save.label=Sauvegarder les sous-titres... diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java index df72230..b315c26 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/fx/FXSubtitlesController.java @@ -18,6 +18,7 @@ import javafx.collections.MapChangeListener; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; +import javafx.scene.control.SelectionMode; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.TableColumn; @@ -165,6 +166,8 @@ public class FXSubtitlesController extends AbstractFXController implements Subti private void bindTable() { subtitlesTable.setItems(model.selectedSubtitles()); + subtitlesTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + subtitlesTable.setOnKeyPressed(e -> { if (e.getCode().isLetterKey() || e.getCode().isDigitKey()) { editFocusedCell(); @@ -180,6 +183,9 @@ public class FXSubtitlesController extends AbstractFXController implements Subti e.consume(); } }); + subtitlesTable.setOnContextMenuRequested(e -> { + //TODO menu with copy, delete + }); startColumn.setCellFactory(TextFieldTableCell.forTableColumn(new TimeStringConverter(timeFormatter))); startColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue() == null ? null : param.getValue().start())); startColumn.setOnEditCommit(e -> { @@ -242,7 +248,7 @@ public class FXSubtitlesController extends AbstractFXController implements Subti @FXML private void addPressed() { - model.selectedCollection().subtitles().add(new ObservableSubtitleImpl("Enter text here...")); + model.selectedCollection().subtitles().add(new ObservableSubtitleImpl(resources.getString("subtitles.add.prompt.label"))); } @FXML diff --git a/whisper/base/src/main/java/com/github/gtache/autosubtitle/setup/whisper/base/WhisperSetupManager.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/setup/whisper/base/WhisperSetupManager.java index 09f9a22..876851a 100644 --- a/whisper/base/src/main/java/com/github/gtache/autosubtitle/setup/whisper/base/WhisperSetupManager.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/setup/whisper/base/WhisperSetupManager.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.setup.whisper.base; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.conda.CondaSetupManager; import com.github.gtache.autosubtitle.setup.whisper.AbstractWhisperSetupManager; @@ -10,6 +11,7 @@ import org.apache.logging.log4j.Logger; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; +import java.net.http.HttpClient; import java.nio.file.Files; import java.time.Duration; import java.util.List; @@ -23,8 +25,9 @@ public class WhisperSetupManager extends AbstractWhisperSetupManager { private static final Logger logger = LogManager.getLogger(WhisperSetupManager.class); @Inject - WhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration) { - super(condaSetupManager, configuration); + WhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration, + final ProcessRunner processRunner, final HttpClient httpClient) { + super(condaSetupManager, configuration, processRunner, httpClient); } @Override @@ -37,7 +40,7 @@ public class WhisperSetupManager extends AbstractWhisperSetupManager { final var path = getPythonPath(); try { logger.info("Installing whisper"); - final var result = run(List.of(path.toString(), "-m", "pip", "install", "-U", "openai-whisper", "numpy<2"), Duration.ofMinutes(15)); + final var result = processRunner().run(List.of(path.toString(), "-m", "pip", "install", "-U", "openai-whisper", "numpy<2"), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Whisper installed"); } else { @@ -53,7 +56,7 @@ public class WhisperSetupManager extends AbstractWhisperSetupManager { final var path = getPythonPath(); if (Files.exists(path)) { try { - final var result = run(List.of(path.toString(), "-m", "pip", "show", "openai-whisper"), Duration.ofSeconds(5)); + final var result = processRunner().run(List.of(path.toString(), "-m", "pip", "show", "openai-whisper"), Duration.ofSeconds(5)); return result.exitCode() == 0; } catch (final IOException e) { throw new SetupException(e); diff --git a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java index 81df659..3912b82 100644 --- a/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java +++ b/whisper/base/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/base/WhisperSubtitleExtractor.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisper.base; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor; @@ -23,8 +24,9 @@ public class WhisperSubtitleExtractor extends AbstractWhisperSubtitleExtractor { @Inject - WhisperSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { - super(venvPath, converterProvider, os); + WhisperSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, + final ProcessRunner processRunner, final OS os) { + super(venvPath, converterProvider, processRunner, os); } @Override diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/setup/whisper/AbstractWhisperSetupManager.java b/whisper/common/src/main/java/com/github/gtache/autosubtitle/setup/whisper/AbstractWhisperSetupManager.java index 4646dba..aa1d57d 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/setup/whisper/AbstractWhisperSetupManager.java +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/setup/whisper/AbstractWhisperSetupManager.java @@ -1,6 +1,7 @@ package com.github.gtache.autosubtitle.setup.whisper; import com.github.gtache.autosubtitle.impl.OS; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupAction; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.SetupManager; @@ -10,6 +11,7 @@ import com.github.gtache.autosubtitle.setup.impl.AbstractSetupManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.net.http.HttpClient; import java.nio.file.Path; import static java.util.Objects.requireNonNull; @@ -26,7 +28,9 @@ public abstract class AbstractWhisperSetupManager extends AbstractSetupManager { private final CondaSetupManager condaSetupManager; private final WhisperSetupConfiguration configuration; - protected AbstractWhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration) { + protected AbstractWhisperSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration, + final ProcessRunner processRunner, final HttpClient httpClient) { + super(processRunner, httpClient); this.condaSetupManager = requireNonNull(condaSetupManager); this.configuration = requireNonNull(configuration); } diff --git a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java index bdcbe59..afd2956 100644 --- a/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java +++ b/whisper/common/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisper/AbstractWhisperSubtitleExtractor.java @@ -5,7 +5,7 @@ import com.github.gtache.autosubtitle.File; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.Video; import com.github.gtache.autosubtitle.impl.OS; -import com.github.gtache.autosubtitle.process.impl.AbstractProcessRunner; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.subtitle.Subtitle; import com.github.gtache.autosubtitle.subtitle.SubtitleCollection; import com.github.gtache.autosubtitle.subtitle.converter.ParseException; @@ -34,7 +34,7 @@ import static java.util.Objects.requireNonNull; /** * Base implementation of {@link SubtitleExtractor} for Whisper */ -public abstract class AbstractWhisperSubtitleExtractor extends AbstractProcessRunner implements SubtitleExtractor { +public abstract class AbstractWhisperSubtitleExtractor implements SubtitleExtractor { private static final Logger logger = LogManager.getLogger(AbstractWhisperSubtitleExtractor.class); @@ -43,13 +43,16 @@ public abstract class AbstractWhisperSubtitleExtractor extends AbstractProcessRu private static final Pattern LINE_PROGRESS_PATTERN = Pattern.compile("^\\[\\d{2}:\\d{2}\\.\\d{3} --> (?\\d{2}):(?\\d{2})\\.(?\\d{3})].+"); private static final Pattern TQDM_PROGRESS_PATTERN = Pattern.compile("^(?\\d{1,3})%\\|.+"); private final Path venvPath; + private final ProcessRunner processRunner; private final SubtitleConverter converter; private final OS os; private final Set listeners; - protected AbstractWhisperSubtitleExtractor(final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { + protected AbstractWhisperSubtitleExtractor(final Path venvPath, final SubtitleConverterProvider converterProvider, + final ProcessRunner processRunner, final OS os) { this.venvPath = requireNonNull(venvPath); this.converter = requireNonNull(converterProvider.getConverter("json")); + this.processRunner = requireNonNull(processRunner); this.os = requireNonNull(os); this.listeners = new HashSet<>(); } @@ -115,7 +118,7 @@ public abstract class AbstractWhisperSubtitleExtractor extends AbstractProcessRu try { final var outputDir = Files.createTempDirectory(AUTOSUBTITLE); final var args = createArgs(path, language, model, outputDir); - final var processListener = startListen(args); + final var processListener = processRunner.startListen(args); var oldProgress = -1.0; var line = processListener.readLine(); while (line != null) { diff --git a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/setup/whisperx/WhisperXSetupManager.java b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/setup/whisperx/WhisperXSetupManager.java index 6f2f422..dc7fe21 100644 --- a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/setup/whisperx/WhisperXSetupManager.java +++ b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/setup/whisperx/WhisperXSetupManager.java @@ -1,5 +1,6 @@ package com.github.gtache.autosubtitle.setup.whisperx; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.setup.SetupException; import com.github.gtache.autosubtitle.setup.conda.CondaSetupManager; import com.github.gtache.autosubtitle.setup.whisper.AbstractWhisperSetupManager; @@ -10,6 +11,7 @@ import org.apache.logging.log4j.Logger; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; +import java.net.http.HttpClient; import java.nio.file.Files; import java.time.Duration; import java.util.List; @@ -23,8 +25,9 @@ public class WhisperXSetupManager extends AbstractWhisperSetupManager { private static final Logger logger = LogManager.getLogger(WhisperXSetupManager.class); @Inject - WhisperXSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration) { - super(condaSetupManager, configuration); + WhisperXSetupManager(final CondaSetupManager condaSetupManager, final WhisperSetupConfiguration configuration, + final ProcessRunner processRunner, final HttpClient httpClient) { + super(condaSetupManager, configuration, processRunner, httpClient); } @Override @@ -37,7 +40,7 @@ public class WhisperXSetupManager extends AbstractWhisperSetupManager { final var path = getPythonPath(); try { logger.info("Installing whisper"); - final var result = run(List.of(path.toString(), "-m", "pip", "install", "-U", "git+https://github.com/m-bain/whisperx.git", "numpy<2"), Duration.ofMinutes(15)); + final var result = processRunner().run(List.of(path.toString(), "-m", "pip", "install", "-U", "git+https://github.com/m-bain/whisperx.git", "numpy<2"), Duration.ofMinutes(15)); if (result.exitCode() == 0) { logger.info("Whisper installed"); } else { @@ -53,7 +56,7 @@ public class WhisperXSetupManager extends AbstractWhisperSetupManager { final var path = getPythonPath(); if (Files.exists(path)) { try { - final var result = run(List.of(path.toString(), "-m", "pip", "show", "whisperx"), Duration.ofSeconds(5)); + final var result = processRunner().run(List.of(path.toString(), "-m", "pip", "show", "whisperx"), Duration.ofSeconds(5)); return result.exitCode() == 0; } catch (final IOException e) { throw new SetupException(e); diff --git a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java index 2c588df..8ed0eff 100644 --- a/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java +++ b/whisper/whisperx/src/main/java/com/github/gtache/autosubtitle/subtitle/extractor/whisperx/WhisperXSubtitleExtractor.java @@ -3,6 +3,7 @@ package com.github.gtache.autosubtitle.subtitle.extractor.whisperx; import com.github.gtache.autosubtitle.Language; import com.github.gtache.autosubtitle.impl.OS; import com.github.gtache.autosubtitle.modules.setup.whisper.WhisperVenvPath; +import com.github.gtache.autosubtitle.process.ProcessRunner; import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.SubtitleExtractor; @@ -22,8 +23,8 @@ import java.util.List; public class WhisperXSubtitleExtractor extends AbstractWhisperSubtitleExtractor { @Inject - WhisperXSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, final OS os) { - super(venvPath, converterProvider, os); + WhisperXSubtitleExtractor(@WhisperVenvPath final Path venvPath, final SubtitleConverterProvider converterProvider, final ProcessRunner processRunner, final OS os) { + super(venvPath, converterProvider, processRunner, os); } @Override