commit 22eab3e09d57c378a91d20a5973b4654975d26ea
parent 536d7254fb9c64b669f7c6e626fd93cb2314dca9
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 30 Jun 2017 17:54:33 -0400
Don't leave file descriptor open in md5Async()
This could cause "Too many open files" errors during file syncing
Diffstat:
2 files changed, 58 insertions(+), 69 deletions(-)
diff --git 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;
}
- );
- }
-
- if (file instanceof OS.File) {
- readChunk(file);
- }
- else {
- if (file instanceof Components.interfaces.nsIFile) {
- var path = file.path;
- }
- else {
- var path = file;
+ // Hex string
+ let hexStr = "";
+ for (let i = 0; i < hash.length; i++) {
+ hexStr += toHexString(hash.charCodeAt(i));
+ }
+ return hexStr;
}
- OS.File.open(path)
- .then(
- function opened(file) {
- readChunk(file);
- },
- function (e) {
- deferred.reject(e);
+ catch (e) {
+ try {
+ ch.finish(false);
}
- );
+ catch (e) {
+ Zotero.logError(e);
+ }
+ throw e;
+ }
+ };
+
+ if (file instanceof OS.File) {
+ 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
@@ -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);
+ });
})