diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileDownloader.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileDownloader.java new file mode 100644 index 0000000000..a7ef60f124 --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileDownloader.java @@ -0,0 +1,38 @@ +package com.baeldung.okhttp.download; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +import java.io.IOException; +import java.util.Objects; + +import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; + +public class BinaryFileDownloader implements AutoCloseable { + + private final OkHttpClient client; + private final BinaryFileWriter writer; + + public BinaryFileDownloader(OkHttpClient client, BinaryFileWriter writer) { + this.client = client; + this.writer = writer; + } + + public long download(String url) throws IOException { + Request request = new Request.Builder().url(url).build(); + Response response = client.newCall(request).execute(); + ResponseBody responseBody = response.body(); + if (responseBody == null) { + throw new IllegalStateException("Response doesn't contain a file"); + } + double length = Double.parseDouble(Objects.requireNonNull(response.header(CONTENT_LENGTH, "1"))); + return writer.write(responseBody.byteStream(), length); + } + + @Override + public void close() throws Exception { + writer.close(); + } +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileWriter.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileWriter.java new file mode 100644 index 0000000000..fb3664c08e --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/BinaryFileWriter.java @@ -0,0 +1,37 @@ +package com.baeldung.okhttp.download; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BinaryFileWriter implements AutoCloseable { + + private static final int CHUNK_SIZE = 1024; + private final OutputStream outputStream; + private final ProgressCallback progressCallback; + + public BinaryFileWriter(OutputStream outputStream, ProgressCallback progressCallback) { + this.outputStream = outputStream; + this.progressCallback = progressCallback; + } + + public long write(InputStream inputStream, double length) throws IOException { + try (BufferedInputStream input = new BufferedInputStream(inputStream)) { + byte[] dataBuffer = new byte[CHUNK_SIZE]; + int readBytes; + long totalBytes = 0; + while ((readBytes = input.read(dataBuffer)) != -1) { + totalBytes += readBytes; + outputStream.write(dataBuffer, 0, readBytes); + progressCallback.onProgress(totalBytes / length * 100.0); + } + return totalBytes; + } + } + + @Override + public void close() throws IOException { + outputStream.close(); + } +} diff --git a/libraries-http-2/src/main/java/com/baeldung/okhttp/download/ProgressCallback.java b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/ProgressCallback.java new file mode 100644 index 0000000000..b57cabf854 --- /dev/null +++ b/libraries-http-2/src/main/java/com/baeldung/okhttp/download/ProgressCallback.java @@ -0,0 +1,7 @@ +package com.baeldung.okhttp.download; + +public interface ProgressCallback { + + void onProgress(double progress); + +} diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderIntegrationTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderIntegrationTest.java new file mode 100644 index 0000000000..6f0f705350 --- /dev/null +++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderIntegrationTest.java @@ -0,0 +1,39 @@ +package com.baeldung.okhttp.download; + +import okhttp3.OkHttpClient; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class BinaryFileDownloaderIntegrationTest { + + @Rule + public MockWebServer server = new MockWebServer(); + + @Test + public void givenATextFile_whenDownload_thenExpectFileDownloaded() { + String body = "Hello Baeldung Readers!"; + server.enqueue(new MockResponse().setBody(body)); + String fileName = "download.txt"; + + ProgressCallback progressCallback = progress -> assertEquals(100.0, progress, .0); + try (BinaryFileWriter writer = new BinaryFileWriter(new FileOutputStream(fileName), progressCallback); BinaryFileDownloader tested = new BinaryFileDownloader(new OkHttpClient(), writer)) { + long downloaded = tested.download(server.url("/greetings").toString()); + assertEquals(body.length(), downloaded); + File downloadedFile = new File(fileName); + assertTrue(downloadedFile.isFile()); + assertTrue(downloadedFile.delete()); + } catch (Exception e) { + fail("An unexpected exception has occurred: " + e); + } + } + +} diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderUnitTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderUnitTest.java new file mode 100644 index 0000000000..15dda3a471 --- /dev/null +++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileDownloaderUnitTest.java @@ -0,0 +1,74 @@ +package com.baeldung.okhttp.download; + +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BinaryFileDownloaderUnitTest { + + @Mock + private OkHttpClient client; + @Mock + private BinaryFileWriter writer; + @InjectMocks + private BinaryFileDownloader tested; + + @Test + public void givenUrlAndResponse_whenDownload_thenExpectFileWritten() throws Exception { + String url = "http://example.com/file"; + Call call = mock(Call.class); + when(client.newCall(any(Request.class))).thenReturn(call); + ResponseBody body = ResponseBody.create("BODY", MediaType.get("application/text")); + Response response = createResponse(url, body); + when(call.execute()).thenReturn(response); + when(writer.write(any(), anyDouble())).thenReturn(1L); + + try (BinaryFileDownloader tested = new BinaryFileDownloader(client, writer)) { + long size = tested.download(url); + assertEquals(1L, size); + verify(writer).write(any(InputStream.class), anyDouble()); + } + verify(writer).close(); + } + + @Test(expected = IllegalStateException.class) + public void givenUrlAndResponseWithNullBody_whenDownload_thenExpectIllegalStateException() throws Exception { + String url = "http://example.com/file"; + Call call = mock(Call.class); + when(client.newCall(any(Request.class))).thenReturn(call); + Response response = createResponse(url, null); + when(call.execute()).thenReturn(response); + + tested.download(url); + + verify(writer, times(0)).write(any(InputStream.class), anyDouble()); + } + + @NotNull + private Response createResponse(String url, ResponseBody body) { + Request request = new Request.Builder().url(url).build(); + return new Response.Builder().code(200).request(request).protocol(Protocol.HTTP_2).message("Message").body(body).build(); + } + +} \ No newline at end of file diff --git a/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileWriterUnitTest.java b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileWriterUnitTest.java new file mode 100644 index 0000000000..2b3f0a1313 --- /dev/null +++ b/libraries-http-2/src/test/java/com/baeldung/okhttp/download/BinaryFileWriterUnitTest.java @@ -0,0 +1,55 @@ +package com.baeldung.okhttp.download; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.InputStream; +import java.io.OutputStream; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BinaryFileWriterUnitTest { + + @Mock + private OutputStream outputStream; + + @Test + public void givenInputStream_whenWrite_thenExpectWritten() throws Exception { + InputStream inputStream = mock(InputStream.class); + when(inputStream.read(any(), anyInt(), anyInt())).thenReturn(10, -1); + + try (BinaryFileWriter tested = new BinaryFileWriter(outputStream, progress -> assertEquals(100.0, progress, .0))) { + long result = tested.write(inputStream, 10); + + assertEquals(10, result); + verify(outputStream).write(any(), eq(0), eq(10)); + verify(inputStream).close(); + } + verify(outputStream).close(); + } + + @Test + public void givenInputStreamEmpty_whenWrite_thenExpectNotWritten() throws Exception { + InputStream inputStream = mock(InputStream.class); + + try (BinaryFileWriter tested = new BinaryFileWriter(outputStream, progress -> assertEquals(100.0, progress, .0))) { + long result = tested.write(inputStream, 1); + + assertEquals(0, result); + verify(outputStream, times(0)).write(any(), anyInt(), anyInt()); + verify(inputStream).close(); + } + verify(outputStream).close(); + } + +} \ No newline at end of file