Bug 941063 - Add a property to get the size of the downloads data written to disk. r=smacleod

This commit is contained in:
Hiroyuki Ikezoe 2015-01-13 13:41:46 +00:00
parent 19e6715d28
commit 8a9bf503ae
2 changed files with 180 additions and 31 deletions

View File

@ -230,6 +230,11 @@ this.Download.prototype = {
* transferred before the download finishes, that can be zero for empty files.
*
* When hasProgress is false, this property is always zero.
*
* @note This property may be different than the final file size on disk for
* downloads that are encoded during the network transfer. You can use
* the "size" property of the DownloadTarget object to get the actual
* size on disk once the download succeeds.
*/
totalBytes: 0,
@ -239,7 +244,8 @@ this.Download.prototype = {
*
* @note You shouldn't rely on this property being equal to totalBytes to
* determine whether the download is completed. You should use the
* individual state properties instead.
* individual state properties instead. This property may not be
* updated during the last part of the download.
*/
currentBytes: 0,
@ -467,7 +473,14 @@ this.Download.prototype = {
yield this.saver.execute(DS_setProgressBytes.bind(this),
DS_setProperties.bind(this));
// Check for the last time if the download has been canceled.
// Now that the actual saving finished, read the actual file size on
// disk, that may be different from the amount of data transferred.
yield this.target.refresh();
// Check for the last time if the download has been canceled. This must
// be done right before setting the "stopped" property of the download,
// without any asynchronous operations in the middle, so that another
// cancellation request cannot start in the meantime and stay unhandled.
if (this._promiseCanceled) {
try {
yield OS.File.remove(this.target.path);
@ -475,6 +488,9 @@ this.Download.prototype = {
Cu.reportError(ex);
}
this.target.exists = false;
this.target.size = 0;
// Cancellation exceptions will be changed in the catch block below.
throw new DownloadError();
}
@ -610,6 +626,7 @@ this.Download.prototype = {
this._promiseUnblock = Task.spawn(function* () {
try {
yield OS.File.move(this.target.partFilePath, this.target.path);
yield this.target.refresh();
} catch (ex) {
yield this.refresh();
this._promiseUnblock = null;
@ -896,6 +913,16 @@ this.Download.prototype = {
return;
}
if (this.succeeded) {
let oldExists = this.target.exists;
let oldSize = this.target.size;
yield this.target.refresh();
if (oldExists != this.target.exists || oldSize != this.target.size) {
this._notifyChange();
}
return;
}
// Update the current progress from disk if we retained partial data.
if ((this.hasPartialData || this.hasBlockedData) &&
this.target.partFilePath) {
@ -1324,6 +1351,58 @@ this.DownloadTarget.prototype = {
*/
partFilePath: null,
/**
* Indicates whether the target file exists.
*
* This is a dynamic property updated when the download finishes or when the
* "refresh" method of the Download object is called. It can be used by the
* front-end to reduce I/O compared to checking the target file directly.
*/
exists: false,
/**
* Size in bytes of the target file, or zero if the download has not finished.
*
* Even if the target file does not exist anymore, this property may still
* have a value taken from the download metadata. If the metadata has never
* been available in this session and the size cannot be obtained from the
* file because it has already been deleted, this property will be zero.
*
* For single-file downloads, this property will always match the actual file
* size on disk, while the totalBytes property of the Download object, when
* available, may represent the size of the encoded data instead.
*
* For downloads involving multiple files, like complete web pages saved to
* disk, the meaning of this value is undefined. It currently matches the size
* of the main file only rather than the sum of all the written data.
*
* This is a dynamic property updated when the download finishes or when the
* "refresh" method of the Download object is called. It can be used by the
* front-end to reduce I/O compared to checking the target file directly.
*/
size: 0,
/**
* Sets the "exists" and "size" properties based on the actual file on disk.
*
* @return {Promise}
* @resolves When the operation has finished successfully.
* @rejects JavaScript exception.
*/
refresh: Task.async(function* () {
try {
this.size = (yield OS.File.stat(this.path)).size;
this.exists = true;
} catch (ex) {
// Report any error not caused by the file not being there. In any case,
// the size of the download is not updated and the known value is kept.
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
Cu.reportError(ex);
}
this.exists = false;
}
}),
/**
* Returns a static representation of the current object state.
*

View File

@ -107,6 +107,26 @@ function promisePartFileReady(aDownload) {
});
}
/**
* Checks that the actual data written to disk matches the expected data as well
* as the properties of the given DownloadTarget object.
*
* @param downloadTarget
* The DownloadTarget object whose details have to be verified.
* @param expectedContents
* String containing the octets that are expected in the file.
*
* @return {Promise}
* @resolves When the properties have been verified.
* @rejects JavaScript exception.
*/
let promiseVerifyTarget = Task.async(function* (downloadTarget,
expectedContents) {
yield promiseVerifyContents(downloadTarget.path, expectedContents);
do_check_true(downloadTarget.exists);
do_check_eq(downloadTarget.size, expectedContents.length);
});
////////////////////////////////////////////////////////////////////////////////
//// Tests
@ -148,7 +168,7 @@ add_task(function test_basic()
// Check additional properties on the finished download.
do_check_true(download.source.referrer === null);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
@ -163,8 +183,7 @@ add_task(function test_basic_tryToKeepPartialData()
yield promiseDownloadStopped(download);
// The target file should now have been created, and the ".part" file deleted.
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_eq(32, download.saver.getSha256Hash().length);
});
@ -287,6 +306,8 @@ add_task(function test_initial_final_state()
do_check_true(download.error === null);
do_check_eq(download.progress, 0);
do_check_true(download.startTime === null);
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
yield download.start();
} else {
@ -302,6 +323,8 @@ add_task(function test_initial_final_state()
do_check_true(download.error === null);
do_check_eq(download.progress, 100);
do_check_true(isValidDate(download.startTime));
do_check_true(download.target.exists);
do_check_eq(download.target.size, TEST_DATA_SHORT.length);
});
/**
@ -348,6 +371,10 @@ add_task(function test_intermediate_progress()
do_check_eq(download.currentBytes, TEST_DATA_SHORT.length);
do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
// The final file size should not be computed for in-progress downloads.
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
// Continue after the first chunk of data is fully received.
continueResponses();
yield promiseDownloadStopped(download);
@ -355,8 +382,7 @@ add_task(function test_intermediate_progress()
do_check_true(download.stopped);
do_check_eq(download.progress, 100);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -377,6 +403,8 @@ add_task(function test_empty_progress()
do_check_eq(download.contentType, "text/plain");
do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
do_check_true(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
@ -406,6 +434,9 @@ add_task(function test_empty_progress_tryToKeepPartialData()
// The target file should now have been created, and the ".part" file deleted.
do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
do_check_true(download.target.exists);
do_check_eq(download.target.size, 0);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_eq(32, download.saver.getSha256Hash().length);
});
@ -481,6 +512,8 @@ add_task(function test_empty_noprogress()
do_check_eq(download.progress, 100);
do_check_eq(download.currentBytes, 0);
do_check_eq(download.totalBytes, 0);
do_check_true(download.target.exists);
do_check_eq(download.target.size, 0);
do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
});
@ -519,8 +552,7 @@ add_task(function test_start_twice()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -577,6 +609,8 @@ add_task(function test_cancel_midway()
do_check_true(download.stopped);
do_check_true(download.canceled);
do_check_true(download.error === null);
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
do_check_false(yield OS.File.exists(download.target.path));
@ -694,8 +728,7 @@ add_task(function test_cancel_midway_restart()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -712,6 +745,8 @@ add_task(function test_cancel_midway_restart_tryToKeepPartialData()
// The target file should not exist, but we should have kept the partial data.
do_check_false(yield OS.File.exists(download.target.path));
yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
// Verify that the server sent the response from the start.
do_check_eq(gMostRecentFirstBytePos, 0);
@ -745,8 +780,7 @@ add_task(function test_cancel_midway_restart_tryToKeepPartialData()
do_check_eq(gMostRecentFirstBytePos, TEST_DATA_SHORT.length);
// The target file should now have been created, and the ".part" file deleted.
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
do_check_false(yield OS.File.exists(download.target.partFilePath));
});
@ -761,11 +795,15 @@ add_task(function test_cancel_midway_restart_removePartialData()
do_check_true(download.hasPartialData);
yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
yield download.removePartialData();
do_check_false(download.hasPartialData);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
// The second time, we'll request and obtain the entire response again.
continueResponses();
@ -775,8 +813,7 @@ add_task(function test_cancel_midway_restart_removePartialData()
do_check_eq(gMostRecentFirstBytePos, 0);
// The target file should now have been created, and the ".part" file deleted.
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
do_check_false(yield OS.File.exists(download.target.partFilePath));
});
@ -831,8 +868,7 @@ add_task(function test_cancel_midway_restart_tryToKeepPartialData_false()
do_check_eq(gMostRecentFirstBytePos, 0);
// The target file should now have been created, and the ".part" file deleted.
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
do_check_false(yield OS.File.exists(download.target.partFilePath));
});
@ -885,8 +921,7 @@ add_task(function test_cancel_immediately_restart_immediately()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -934,8 +969,7 @@ add_task(function test_cancel_midway_restart_immediately()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -954,7 +988,7 @@ add_task(function test_cancel_successful()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
@ -993,6 +1027,31 @@ add_task(function test_cancel_twice()
do_check_false(yield OS.File.exists(download.target.path));
});
/**
* Checks the "refresh" method for succeeded downloads.
*/
add_task(function test_refresh_succeeded()
{
let download = yield promiseStartDownload();
yield promiseDownloadStopped(download);
// The DownloadTarget properties should be the same after calling "refresh".
yield download.refresh();
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
// If the file is removed, only the "exists" property should change, and the
// "size" property should keep its previous value.
yield OS.File.move(download.target.path, download.target.path + ".old");
yield download.refresh();
do_check_false(download.target.exists);
do_check_eq(download.target.size, TEST_DATA_SHORT.length);
// The DownloadTarget properties should be restored when the file is put back.
yield OS.File.move(download.target.path + ".old", download.target.path);
yield download.refresh();
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
* Checks that a download cannot be restarted after the "finalize" method.
*/
@ -1080,8 +1139,7 @@ add_task(function test_whenSucceeded_after_restart()
do_check_false(download.canceled);
do_check_true(download.error === null);
yield promiseVerifyContents(download.target.path,
TEST_DATA_SHORT + TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -1124,6 +1182,8 @@ add_task(function test_error_source()
do_check_false(download.error.becauseTargetFailed);
do_check_false(yield OS.File.exists(download.target.path));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
} finally {
serverSocket.close();
}
@ -1177,6 +1237,8 @@ add_task(function test_error_source_partial()
do_check_eq(download.error.result, Cr.NS_ERROR_NET_PARTIAL_TRANSFER);
do_check_false(yield OS.File.exists(download.target.path));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
@ -1276,7 +1338,7 @@ add_task(function test_error_restart()
do_check_true(download.error === null);
do_check_eq(download.progress, 100);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
@ -1387,7 +1449,7 @@ add_task(function test_with_content_encoding()
do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
// Ensure the content matches the decoded test data.
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
cleanup();
});
@ -1421,10 +1483,11 @@ add_task(function test_with_content_encoding_ignore_extension()
do_check_eq(download.progress, 100);
do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
do_check_eq(download.target.size, TEST_DATA_SHORT_GZIP_ENCODED.length);
// Ensure the content matches the encoded test data. We convert the data to a
// string before executing the content check.
yield promiseVerifyContents(download.target.path,
yield promiseVerifyTarget(download.target,
String.fromCharCode.apply(String, TEST_DATA_SHORT_GZIP_ENCODED));
cleanup();
@ -1465,7 +1528,7 @@ add_task(function test_cancel_midway_restart_with_content_encoding()
do_check_eq(download.progress, 100);
do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
});
/**
@ -1547,7 +1610,6 @@ add_task(function test_getSha256Hash()
}
});
/**
* Create a download which will be reputation blocked.
*
@ -1613,6 +1675,8 @@ add_task(function test_blocked_applicationReputation()
// Now that the download is blocked, the target file should not exist.
do_check_false(yield OS.File.exists(download.target.path));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
// There should also be no blocked data in this case
do_check_false(download.hasBlockedData);
@ -1641,6 +1705,8 @@ add_task(function test_blocked_applicationReputation_confirmBlock()
do_check_false(download.hasBlockedData);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_false(yield OS.File.exists(download.target.path));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
@ -1665,7 +1731,7 @@ add_task(function test_blocked_applicationReputation_unblock()
do_check_true(download.succeeded);
do_check_false(download.hasBlockedData);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_true(yield OS.File.exists(download.target.path));
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT + TEST_DATA_SHORT);
// The only indication the download was previously blocked is the
// existence of the error, so we make sure it's still set.
@ -1743,6 +1809,8 @@ add_task(function test_blocked_applicationReputation_decisionRace()
do_check_false(download.hasBlockedData);
do_check_false(yield OS.File.exists(download.target.partFilePath));
do_check_false(yield OS.File.exists(download.target.path));
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
@ -1772,6 +1840,8 @@ add_task(function test_blocked_applicationReputation_unblock()
do_check_false(download.hasBlockedData);
do_check_true(download.stopped);
do_check_false(download.succeeded);
do_check_false(download.target.exists);
do_check_eq(download.target.size, 0);
});
/**
@ -2034,7 +2104,7 @@ add_task(function test_platform_integration()
// Then, wait for the promise returned by "start" to be resolved.
yield promiseDownloadStopped(download);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
yield promiseVerifyTarget(download.target, TEST_DATA_SHORT);
}
});