Don't leave file descriptor open in md5Async()

This could cause "Too many open files" errors during file syncing
This commit is contained in:
Dan Stillman 2017-06-30 17:54:33 -04:00
parent 536d7254fb
commit 22eab3e09d
2 changed files with 58 additions and 69 deletions

View file

@ -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;
},

View file

@ -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);
});
})