--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
@@ -82,16 +82,21 @@ public class DownloadAction extends Base
if (destinationFile.exists() && verify(destinationFile, content.getChecksum())) {
Log.d(LOGTAG, "Content already exists and is up-to-date.");
catalog.markAsDownloaded(content);
continue;
}
temporaryFile = createTemporaryFile(context, content);
+ if (!hasEnoughDiskSpace(content, destinationFile, temporaryFile)) {
+ Log.d(LOGTAG, "Not enough disk space to save content. Skipping download.");
+ continue;
+ }
+
// TODO: Check space on disk before downloading content (bug 1220145)
final String url = createDownloadURL(content);
if (!temporaryFile.exists() || temporaryFile.length() < content.getSize()) {
download(client, url, temporaryFile);
}
if (!verify(temporaryFile, content.getDownloadChecksum())) {
@@ -304,9 +309,24 @@ public class DownloadAction extends Base
// We could not copy the temporary file to its destination: Keep the temporary file and
// try again the next time we run.
throw new RecoverableDownloadContentException(e);
} finally {
IOUtils.safeStreamClose(inputStream);
IOUtils.safeStreamClose(outputStream);
}
}
+
+ protected boolean hasEnoughDiskSpace(DownloadContent content, File destinationFile, File temporaryFile) {
+ final File temporaryDirectory = temporaryFile.getParentFile();
+ if (temporaryDirectory.getUsableSpace() < content.getSize()) {
+ return false;
+ }
+
+ final File destinationDirectory = destinationFile.getParentFile();
+ // We need some more space to extract the file (getSize() returns the uncompressed size)
+ if (destinationDirectory.getUsableSpace() < content.getSize() * 2) {
+ return false;
+ }
+
+ return true;
+ }
}
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestDownloadAction.java
@@ -16,38 +16,29 @@ import org.mozilla.gecko.dlc.catalog.Dow
import android.content.Context;
import org.robolectric.RuntimeEnvironment;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.HttpStatus;
import ch.boye.httpclientandroidlib.StatusLine;
import ch.boye.httpclientandroidlib.client.HttpClient;
import ch.boye.httpclientandroidlib.client.methods.HttpGet;
import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
/**
* DownloadAction: Download content that has been scheduled during "study" or "verify".
*/
@RunWith(TestRunner.class)
public class TestDownloadAction {
private static final String TEST_URL = "http://example.org";
@@ -168,18 +159,17 @@ public class TestDownloadAction {
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
- File file = mock(File.class);
- doReturn(false).when(file).exists();
+ File file = mockNotExistingFile();
doReturn(file).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(false).when(action).verify(eq(file), anyString());
doNothing().when(action).download(any(HttpClient.class), anyString(), eq(file));
doReturn(true).when(action).verify(eq(file), anyString());
doNothing().when(action).extract(eq(file), eq(file), anyString());
@@ -208,26 +198,26 @@ public class TestDownloadAction {
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
- File temporaryFile = mockTemporaryFile(true, 1337L);
+ File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());
HttpClient client = mockHttpClient(HttpStatus.SC_PARTIAL_CONTENT, "HelloWorld");
doReturn(client).when(action).buildHttpClient();
- File destinationFile = mock(File.class);
+ File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(temporaryFile), anyString());
doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
action.perform(RuntimeEnvironment.application, catalog);
ArgumentCaptor<HttpGet> argument = ArgumentCaptor.forClass(HttpGet.class);
@@ -259,32 +249,32 @@ public class TestDownloadAction {
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
- File temporaryFile = mockTemporaryFile(true, 1337L);
+ File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
ByteArrayOutputStream outputStream = spy(new ByteArrayOutputStream());
doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());
doThrow(IOException.class).when(outputStream).write(any(byte[].class), anyInt(), anyInt());
HttpClient client = mockHttpClient(HttpStatus.SC_PARTIAL_CONTENT, "HelloWorld");
doReturn(client).when(action).buildHttpClient();
- File destinationFile = mock(File.class);
- doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
+ doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content);
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog, never()).markAsDownloaded(content);
+ verify(action, never()).verify(any(File.class), anyString());
verify(temporaryFile, never()).delete();
}
/**
* Scenario: Partially downloaded file is already complete.
*
* Verify that:
* * No download request is made
@@ -300,20 +290,20 @@ public class TestDownloadAction {
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
- File temporaryFile = mockTemporaryFile(true, 1337L);
+ File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
- File destinationFile = mock(File.class);
+ File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(temporaryFile), anyString());
doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(action, never()).download(any(HttpClient.class), anyString(), eq(temporaryFile));
@@ -341,34 +331,142 @@ public class TestDownloadAction {
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
doNothing().when(action).download(any(HttpClient.class), anyString(), any(File.class));
doReturn(false).when(action).verify(any(File.class), anyString());
- File temporaryFile = mockTemporaryFile(true, 1337L);
+ File temporaryFile = mockNotExistingFile();
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
- File destinationFile = mock(File.class);
+ File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
action.perform(RuntimeEnvironment.application, catalog);
verify(temporaryFile).delete();
verify(action, never()).extract(any(File.class), any(File.class), anyString());
verify(catalog, never()).markAsDownloaded(content);
}
- private static File mockTemporaryFile(boolean exists, long length) {
- File temporaryFile = mock(File.class);
- doReturn(exists).when(temporaryFile).exists();
- doReturn(length).when(temporaryFile).length();
- return temporaryFile;
+ /**
+ * Scenario: Not enough storage space for content is available.
+ *
+ * Verify that:
+ * * No download will per performed
+ */
+ @Test
+ public void testNoDownloadIsPerformedIfNotEnoughStorageIsAvailable() throws Exception {
+ DownloadContent content = createFontWithSize(1337L);
+ DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);
+
+ DownloadAction action = spy(new DownloadAction(null));
+ doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
+ doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
+
+ File temporaryFile = mockNotExistingFile();
+ doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
+
+ File destinationFile = mockNotExistingFile();
+ doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
+
+ doReturn(true).when(action).hasEnoughDiskSpace(content, destinationFile, temporaryFile);
+
+ verify(action, never()).buildHttpClient();
+ verify(action, never()).download(any(HttpClient.class), anyString(), any(File.class));
+ verify(action, never()).verify(any(File.class), anyString());
+ verify(catalog, never()).markAsDownloaded(content);
+ }
+
+ /**
+ * Scenario: Not enough storage space for temporary file available.
+ *
+ * Verify that:
+ * * hasEnoughDiskSpace() returns false
+ */
+ @Test
+ public void testWithNotEnoughSpaceForTemporaryFile() {
+ DownloadContent content = createFontWithSize(2048);
+ File destinationFile = mockNotExistingFile();
+ File temporaryFile = mockNotExistingFileWithUsableSpace(1024);
+
+ DownloadAction action = new DownloadAction(null);
+ Assert.assertFalse(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
+ }
+
+ /**
+ * Scenario: Not enough storage space for destination file available.
+ *
+ * Verify that:
+ * * hasEnoughDiskSpace() returns false
+ */
+ @Test
+ public void testWithNotEnoughSpaceForDestinationFile() {
+ DownloadContent content = createFontWithSize(2048);
+ File destinationFile = mockNotExistingFileWithUsableSpace(1024);
+ File temporaryFile = mockNotExistingFile();
+
+ DownloadAction action = new DownloadAction(null);
+ Assert.assertFalse(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
+ }
+
+ /**
+ * Scenario: Enough storage space for temporary and destination file available.
+ *
+ * Verify that:
+ * * hasEnoughDiskSpace() returns true
+ */
+ @Test
+ public void testWithEnoughSpaceForEverything() {
+ DownloadContent content = createFontWithSize(2048);
+ File destinationFile = mockNotExistingFileWithUsableSpace(4096);
+ File temporaryFile = mockNotExistingFileWithUsableSpace(4096);
+
+ DownloadAction action = new DownloadAction(null);
+ Assert.assertTrue(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
+ }
+
+ private DownloadContent createFontWithSize(long size) {
+ return new DownloadContent.Builder()
+ .setKind(DownloadContent.KIND_FONT)
+ .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
+ .setSize(size)
+ .build();
+ }
+
+ private DownloadContentCatalog mockCatalogWithScheduledDownloads(DownloadContent... content) {
+ DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+ doReturn(Arrays.asList(content)).when(catalog).getScheduledDownloads();
+ return catalog;
+ }
+
+ private static File mockNotExistingFile() {
+ return mockFileWithUsableSpace(false, 0, Long.MAX_VALUE);
+ }
+
+ private static File mockNotExistingFileWithUsableSpace(long usableSpace) {
+ return mockFileWithUsableSpace(false, 0, usableSpace);
+ }
+
+ private static File mockFileWithSize(long length) {
+ return mockFileWithUsableSpace(true, length, Long.MAX_VALUE);
+ }
+
+ private static File mockFileWithUsableSpace(boolean exists, long length, long usableSpace) {
+ File file = mock(File.class);
+ doReturn(exists).when(file).exists();
+ doReturn(length).when(file).length();
+
+ File parentFile = mock(File.class);
+ doReturn(usableSpace).when(parentFile).getUsableSpace();
+ doReturn(parentFile).when(file).getParentFile();
+
+ return file;
}
private static HttpClient mockHttpClient(int statusCode, String content) throws Exception {
StatusLine status = mock(StatusLine.class);
doReturn(statusCode).when(status).getStatusCode();
HttpEntity entity = mock(HttpEntity.class);
doReturn(new ByteArrayInputStream(content.getBytes("UTF-8"))).when(entity).getContent();