diff --git a/src/main/java/me/itzg/helpers/get/GetCommand.java b/src/main/java/me/itzg/helpers/get/GetCommand.java index d78f1a4d..7e80f571 100644 --- a/src/main/java/me/itzg/helpers/get/GetCommand.java +++ b/src/main/java/me/itzg/helpers/get/GetCommand.java @@ -134,6 +134,11 @@ public class GetCommand implements Callable { @Option(names = "--retry-delay", description = "in seconds", defaultValue = "2") int retryDelay; + @Option(names = {"--use-temp-dir"}, + description = "Specifies the name of a directory to use for downloading to a temporary file, before it is copied to the final destination", + paramLabel = "DIR") + Path tempDir; + @Parameters(split = OPTION_SPLIT_COMMAS, paramLabel = "URI", description = "The URI of the resource to retrieve. When the output is a directory," + " more than one URI can be requested.", @@ -159,6 +164,10 @@ public Integer call() throws IOException { throw new ParameterException(spec.commandLine(), "No URIs were given"); } + if (tempDir != null && !Files.isDirectory(tempDir)) { + throw new ParameterException(spec.commandLine(), "The supplied temporary directory does not exist"); + } + final LatchingUrisInterceptor interceptor = new LatchingUrisInterceptor(); try (CloseableHttpClient client = HttpClients.custom() @@ -209,8 +218,9 @@ null, new JsonPathOutputHandler( stdout.println(outputFile); } } else { + final Path saveToFile = getSaveToFile(); final Path file = fetch(uris.get(0)) - .toFile(outputFile) + .toFile(saveToFile) .skipUpToDate(skipUpToDate) .acceptContentTypes(acceptContentTypes) .handleDownloaded((uri, f, contentSizeBytes) -> { @@ -219,6 +229,9 @@ null, new JsonPathOutputHandler( } }) .execute(); + if (tempDir != null) { + Files.move(saveToFile, outputFile); + } if (this.outputFilename) { stdout.println(file); } @@ -477,5 +490,8 @@ private static URI removeUserInfo(URI uri) throws URISyntaxException { ); } + private Path getSaveToFile() { + return tempDir == null ? outputFile : tempDir.resolve(outputFile.getFileName()); + } } diff --git a/src/test/java/me/itzg/helpers/get/GetCommandTest.java b/src/test/java/me/itzg/helpers/get/GetCommandTest.java index dc64d5b4..d1d19add 100644 --- a/src/test/java/me/itzg/helpers/get/GetCommandTest.java +++ b/src/test/java/me/itzg/helpers/get/GetCommandTest.java @@ -12,9 +12,12 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; import me.itzg.helpers.TestLoggingAppender; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import picocli.CommandLine; import picocli.CommandLine.ExitCode; @@ -222,4 +225,49 @@ void tryHttps(WireMockRuntimeInfo wmInfo) throws Exception { // See https://wiremock.org/docs/https/#common-https-issues assertThat(err).contains("unable to find valid certification path to requested target"); } + + @Test + void failWhenSuppliedTempDirDoesntExist() throws Exception { + final String stderr = tapSystemErrNormalized(() -> { + + final int status = + new CommandLine(new GetCommand()) + .execute( + "--use-temp-dir", + "non-existent-temp-directory", + "-o", + "unused.out", + "http://unused" + ); + + assertThat(status).isEqualTo(ExitCode.USAGE); + }); + + assertThat(stderr) + .contains("The supplied temporary directory does not exist"); + } + + @Test + void failWhenSuppliedTempDirIsAFile(@TempDir Path tempDir) throws Exception { + final Path expectedFile = tempDir.resolve("isafile"); + Files.createFile(expectedFile); + + final String stderr = tapSystemErrNormalized(() -> { + + final int status = + new CommandLine(new GetCommand()) + .execute( + "--use-temp-dir", + expectedFile.toString(), + "-o", + "unused.out", + "http://unused" + ); + + assertThat(status).isEqualTo(ExitCode.USAGE); + }); + + assertThat(stderr) + .contains("The supplied temporary directory does not exist"); + } } diff --git a/src/test/java/me/itzg/helpers/get/OutputToFileTest.java b/src/test/java/me/itzg/helpers/get/OutputToFileTest.java index d9e192bd..891a21dd 100644 --- a/src/test/java/me/itzg/helpers/get/OutputToFileTest.java +++ b/src/test/java/me/itzg/helpers/get/OutputToFileTest.java @@ -230,4 +230,30 @@ void skipsUpToDate_butDownloadsWhenAbsent(@TempDir Path tempDir) throws IOExcept assertThat(fileToDownload).hasContent("New content"); } + @Test + void successfulWithTemporaryDirectory(@TempDir Path tempDir) throws MalformedURLException, IOException { + mock.expectRequest("GET", "/downloadsToFile.txt", + response() + .withBody("Response content to file", MediaType.TEXT_PLAIN) + ); + + final Path expectedFile = tempDir.resolve("out.txt"); + final Path downloadingDir = tempDir.resolve("downloading"); + Files.createDirectory(downloadingDir); + + final int status = + new CommandLine(new GetCommand()) + .execute( + "-o", + expectedFile.toString(), + "--use-temp-dir", + downloadingDir.toString(), + mock.buildMockedUrl("/downloadsToFile.txt").toString() + ); + + assertThat(status).isEqualTo(0); + assertThat(expectedFile).exists(); + assertThat(expectedFile).hasContent("Response content to file"); + } + }