Don't leave file descriptor open in md5Async()
This could cause "Too many open files" errors during file syncing
This commit is contained in:
parent
536d7254fb
commit
22eab3e09d
2 changed files with 58 additions and 69 deletions
|
@ -138,11 +138,9 @@ Zotero.Utilities.Internal = {
|
||||||
* @param {Boolean} [base64=FALSE] Return as base-64-encoded string
|
* @param {Boolean} [base64=FALSE] Return as base-64-encoded string
|
||||||
* rather than hex string
|
* rather than hex string
|
||||||
*/
|
*/
|
||||||
"md5Async": function (file, base64) {
|
md5Async: async function (file, base64) {
|
||||||
const CHUNK_SIZE = 16384;
|
const CHUNK_SIZE = 16384;
|
||||||
|
|
||||||
var deferred = Zotero.Promise.defer();
|
|
||||||
|
|
||||||
function toHexString(charCode) {
|
function toHexString(charCode) {
|
||||||
return ("0" + charCode.toString(16)).slice(-2);
|
return ("0" + charCode.toString(16)).slice(-2);
|
||||||
}
|
}
|
||||||
|
@ -151,77 +149,52 @@ Zotero.Utilities.Internal = {
|
||||||
.createInstance(Components.interfaces.nsICryptoHash);
|
.createInstance(Components.interfaces.nsICryptoHash);
|
||||||
ch.init(ch.MD5);
|
ch.init(ch.MD5);
|
||||||
|
|
||||||
// Recursively read chunks of the file, and resolve the promise
|
// Recursively read chunks of the file and return a promise for the hash
|
||||||
// with the hash when done
|
let readChunk = async function (file) {
|
||||||
let readChunk = function readChunk(file) {
|
try {
|
||||||
file.read(CHUNK_SIZE)
|
let data = await file.read(CHUNK_SIZE);
|
||||||
.then(
|
ch.update(data, data.length);
|
||||||
function readSuccess(data) {
|
if (data.length == CHUNK_SIZE) {
|
||||||
ch.update(data, data.length);
|
return readChunk(file);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.then(
|
let hash = ch.finish(base64);
|
||||||
null,
|
// Base64
|
||||||
function (e) {
|
if (base64) {
|
||||||
try {
|
return hash;
|
||||||
ch.finish(false);
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
|
|
||||||
deferred.reject(e);
|
|
||||||
}
|
}
|
||||||
);
|
// 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) {
|
if (file instanceof OS.File) {
|
||||||
readChunk(file);
|
return 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 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;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,23 @@ describe("Zotero.Utilities.Internal", function () {
|
||||||
Zotero.Utilities.Internal.md5Async(file),
|
Zotero.Utilities.Internal.md5Async(file),
|
||||||
'93da8f1e5774c599f0942dcecf64b11c'
|
'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);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue