diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index d5935527ee..e37c211da1 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -138,11 +138,9 @@ Zotero.Utilities.Internal = { * @param {Boolean} [base64=FALSE] Return as base-64-encoded string * rather than hex string */ - "md5Async": function (file, base64) { + md5Async: async function (file, base64) { const CHUNK_SIZE = 16384; - var deferred = Zotero.Promise.defer(); - function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } @@ -151,77 +149,52 @@ Zotero.Utilities.Internal = { .createInstance(Components.interfaces.nsICryptoHash); ch.init(ch.MD5); - // Recursively read chunks of the file, and resolve the promise - // with the hash when done - let readChunk = function readChunk(file) { - file.read(CHUNK_SIZE) - .then( - function readSuccess(data) { - ch.update(data, data.length); - if (data.length == CHUNK_SIZE) { - readChunk(file); - } - else { - let hash = ch.finish(base64); - - // Base64 - if (base64) { - deferred.resolve(hash); - } - // Hex string - else { - let hexStr = ""; - for (let i = 0; i < hash.length; i++) { - hexStr += toHexString(hash.charCodeAt(i)); - } - deferred.resolve(hexStr); - } - } - }, - function (e) { - try { - ch.finish(false); - } - catch (e) {} - - deferred.reject(e); + // Recursively read chunks of the file and return a promise for the hash + let readChunk = async function (file) { + try { + let data = await file.read(CHUNK_SIZE); + ch.update(data, data.length); + if (data.length == CHUNK_SIZE) { + return readChunk(file); } - ) - .then( - null, - function (e) { - try { - ch.finish(false); - } - catch (e) {} - - deferred.reject(e); + + let hash = ch.finish(base64); + // Base64 + if (base64) { + return hash; } - ); - } + // Hex string + let hexStr = ""; + for (let i = 0; i < hash.length; i++) { + hexStr += toHexString(hash.charCodeAt(i)); + } + return hexStr; + } + catch (e) { + try { + ch.finish(false); + } + catch (e) { + Zotero.logError(e); + } + throw e; + } + }; if (file instanceof OS.File) { - readChunk(file); - } - else { - if (file instanceof Components.interfaces.nsIFile) { - var path = file.path; - } - else { - var path = file; - } - OS.File.open(path) - .then( - function opened(file) { - readChunk(file); - }, - function (e) { - deferred.reject(e); - } - ); + return readChunk(file); } - return deferred.promise; + var path = (file instanceof Components.interfaces.nsIFile) ? file.path : file; + var hash; + try { + file = await OS.File.open(path); + hash = await readChunk(file); + } + finally { + await file.close(); + } + return hash; }, diff --git a/test/tests/utilities_internalTest.js b/test/tests/utilities_internalTest.js index 29af9e1f50..ea6eeae753 100644 --- a/test/tests/utilities_internalTest.js +++ b/test/tests/utilities_internalTest.js @@ -19,7 +19,23 @@ describe("Zotero.Utilities.Internal", function () { Zotero.Utilities.Internal.md5Async(file), '93da8f1e5774c599f0942dcecf64b11c' ); - }) + }); + + it("should generate hex string given file path for file bigger than chunk size", function* () { + var tmpDir = Zotero.getTempDirectory().path; + var file = OS.Path.join(tmpDir, 'md5Async'); + + let encoder = new TextEncoder(); + let arr = encoder.encode("".padStart(100000, "a")); + yield OS.File.writeAtomic(file, arr); + + yield assert.eventually.equal( + Zotero.Utilities.Internal.md5Async(file), + '1af6d6f2f682f76f80e606aeaaee1680' + ); + + yield OS.File.remove(file); + }); })