diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginInstallation.java b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginInstallation.java index 366558f3c9..fa59930a78 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginInstallation.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PendingPluginInstallation.java @@ -3,16 +3,18 @@ package sonia.scm.plugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; class PendingPluginInstallation { private static final Logger LOG = LoggerFactory.getLogger(PendingPluginInstallation.class); private final AvailablePlugin plugin; - private final File file; + private final Path file; - PendingPluginInstallation(AvailablePlugin plugin, File file) { + PendingPluginInstallation(AvailablePlugin plugin, Path file) { this.plugin = plugin; this.file = file; } @@ -24,8 +26,10 @@ class PendingPluginInstallation { void cancel() { String name = plugin.getDescriptor().getInformation().getName(); LOG.info("cancel installation of plugin {}", name); - if (!file.delete()) { - throw new PluginFailedToCancelInstallationException("failed to cancel installation of plugin " + name); + try { + Files.delete(file); + } catch (IOException ex) { + throw new PluginFailedToCancelInstallationException("failed to cancel installation of plugin " + name, ex); } } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginFailedToCancelInstallationException.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginFailedToCancelInstallationException.java index 2bb6db8125..e3d6c123d6 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginFailedToCancelInstallationException.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginFailedToCancelInstallationException.java @@ -1,7 +1,7 @@ package sonia.scm.plugin; public class PluginFailedToCancelInstallationException extends RuntimeException { - public PluginFailedToCancelInstallationException(String message) { - super(message); + public PluginFailedToCancelInstallationException(String message, Throwable cause) { + super(message, cause); } } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java index e512ea3212..b001059ea0 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginInstaller.java @@ -1,20 +1,20 @@ package sonia.scm.plugin; +import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; -import com.google.common.io.ByteStreams; -import com.google.common.io.Files; +import com.google.common.hash.HashingInputStream; import sonia.scm.SCMContextProvider; import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.util.IOUtil; import javax.inject.Inject; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Optional; +@SuppressWarnings("UnstableApiUsage") // guava hash is marked as unstable class PluginInstaller { private final SCMContextProvider context; @@ -27,24 +27,24 @@ class PluginInstaller { } public PendingPluginInstallation install(AvailablePlugin plugin) { - File file = createFile(plugin); - try (InputStream input = download(plugin); OutputStream output = new FileOutputStream(file)) { - ByteStreams.copy(input, output); + try (HashingInputStream input = new HashingInputStream(Hashing.sha256(), download(plugin))) { + Path file = createFile(plugin); + Files.copy(input, file); - verifyChecksum(plugin, file); + verifyChecksum(plugin, input.hash()); // TODO clean up in case of error return new PendingPluginInstallation(plugin, file); } catch (IOException ex) { - throw new PluginDownloadException("failed to install plugin", ex); + throw new PluginDownloadException("failed to download plugin", ex); } } - private void verifyChecksum(AvailablePlugin plugin, File file) throws IOException { + private void verifyChecksum(AvailablePlugin plugin, HashCode hash) { Optional checksum = plugin.getDescriptor().getChecksum(); if (checksum.isPresent()) { - String calculatedChecksum = Files.hash(file, Hashing.sha256()).toString(); + String calculatedChecksum = hash.toString(); if (!checksum.get().equalsIgnoreCase(calculatedChecksum)) { throw new PluginChecksumMismatchException( String.format("downloaded plugin checksum %s does not match expected %s", calculatedChecksum, checksum.get()) @@ -57,9 +57,9 @@ class PluginInstaller { return client.get(plugin.getDescriptor().getUrl()).request().contentAsStream(); } - private File createFile(AvailablePlugin plugin) { - File pluginDirectory = new File(context.getBaseDirectory(), "plugins"); - IOUtil.mkdirs(pluginDirectory); - return new File(pluginDirectory, plugin.getDescriptor().getInformation().getName() + ".smp"); + private Path createFile(AvailablePlugin plugin) throws IOException { + Path directory = context.resolve(Paths.get("plugins")); + Files.createDirectories(directory); + return directory.resolve(plugin.getDescriptor().getInformation().getName() + ".smp"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PendingPluginInstallationTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PendingPluginInstallationTest.java index 0e4ff9cbce..ae61d6e367 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PendingPluginInstallationTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PendingPluginInstallationTest.java @@ -28,7 +28,7 @@ class PendingPluginInstallationTest { when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin"); - PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file.toFile()); + PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file); installation.cancel(); assertThat(file).doesNotExist(); @@ -39,7 +39,7 @@ class PendingPluginInstallationTest { Path file = directory.resolve("file"); when(plugin.getDescriptor().getInformation().getName()).thenReturn("scm-awesome-plugin"); - PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file.toFile()); + PendingPluginInstallation installation = new PendingPluginInstallation(plugin, file); assertThrows(PluginFailedToCancelInstallationException.class, installation::cancel); } diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java index e209641222..0aa99c358c 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginInstallerTest.java @@ -20,6 +20,8 @@ import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.in; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; @ExtendWith({MockitoExtension.class, TempDirectory.class}) @@ -39,7 +41,10 @@ class PluginInstallerTest { @BeforeEach void setUpContext(@TempDirectory.TempDir Path directory) { this.directory = directory; - when(context.getBaseDirectory()).thenReturn(directory.toFile()); + lenient().when(context.resolve(any())).then(ic -> { + Path arg = ic.getArgument(0); + return directory.resolve(arg); + }); } @Test