diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index c1b6298c0..d89bd0761 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -109,14 +109,7 @@ Zotero.File = new function(){ */ this.getSample = function (file) { var bytes = 200; - return this.getContentsAsync(file, null, bytes) - .catch(function (e) { - if (e.name == 'NS_ERROR_ILLEGAL_INPUT') { - Zotero.debug("Falling back to raw bytes"); - return this.getBinaryContentsAsync(file, bytes); - } - throw e; - }.bind(this)); + return this.getContentsAsync(file, null, bytes); } @@ -199,110 +192,119 @@ Zotero.File = new function(){ /** * Get the contents of a text source asynchronously * - * @param {nsIURI|nsIFile|string spec|string path|nsIChannel|nsIInputStream} source The source to read + * @param {string path|nsIFile|file URI|nsIChannel|nsIInputStream} source The source to read * @param {String} [charset] The character set; defaults to UTF-8 * @param {Integer} [maxLength] Maximum length to fetch, in bytes * @return {Promise} A promise that is resolved with the contents of the file */ - this.getContentsAsync = function (source, charset, maxLength) { + this.getContentsAsync = Zotero.Promise.coroutine(function* (source, charset, maxLength) { Zotero.debug("Getting contents of " + (source instanceof Components.interfaces.nsIFile ? source.path : (source instanceof Components.interfaces.nsIInputStream ? "input stream" : source))); - // If path is given, convert to file:// URL - if (typeof source == 'string' && !source.match(/^file:/)) { - source = 'file://' + source; + // Send URIs to Zotero.HTTP.request() + if (source instanceof Components.interfaces.nsIURI + || typeof source == 'string' && !source.startsWith('file:') && source.match(/^[a-z]{3,}:/)) { + Zotero.logError("Passing a URI to Zotero.File.getContentsAsync() is deprecated " + + "-- use Zotero.HTTP.request() instead"); + return Zotero.HTTP.request("GET", source); } - var options = { - charset: charset ? charset : "UTF-8", - replacement: 65533 - }; - - var deferred = Zotero.Promise.defer(); - try { - NetUtil.asyncFetch(source, function(inputStream, status) { - if (!Components.isSuccessCode(status)) { - deferred.reject(new Components.Exception("File read operation failed", status)); - return; - } - - try { - try { - var bytesToFetch = inputStream.available(); - } - catch (e) { - // The stream is closed automatically when end-of-file is reached, - // so this throws for empty files - if (e.name == "NS_BASE_STREAM_CLOSED") { - Zotero.debug("RESOLVING2"); - deferred.resolve(""); - } - deferred.reject(e); - } - - if (maxLength && maxLength < bytesToFetch) { - bytesToFetch = maxLength; - } - - if (bytesToFetch == 0) { - deferred.resolve(""); + // Use NetUtil.asyncFetch() for input streams and channels + if (source instanceof Components.interfaces.nsIInputStream + || source instanceof Components.interfaces.nsIChannel) { + var deferred = Zotero.Promise.defer(); + try { + NetUtil.asyncFetch(source, function(inputStream, status) { + if (!Components.isSuccessCode(status)) { + deferred.reject(new Components.Exception("File read operation failed", status)); return; } - deferred.resolve(NetUtil.readInputStreamToString( - inputStream, - bytesToFetch, - options - )); - } - catch (e) { - deferred.reject(e); - } - }); + try { + try { + var bytesToFetch = inputStream.available(); + } + catch (e) { + // The stream is closed automatically when end-of-file is reached, + // so this throws for empty files + if (e.name == "NS_BASE_STREAM_CLOSED") { + Zotero.debug("RESOLVING2"); + deferred.resolve(""); + } + deferred.reject(e); + } + + if (maxLength && maxLength < bytesToFetch) { + bytesToFetch = maxLength; + } + + if (bytesToFetch == 0) { + deferred.resolve(""); + return; + } + + deferred.resolve(NetUtil.readInputStreamToString( + inputStream, + bytesToFetch, + options + )); + } + catch (e) { + deferred.reject(e); + } + }); + } + catch(e) { + // Make sure this get logged correctly + Zotero.logError(e); + throw e; + } + return deferred.promise; } - catch(e) { - // Make sure this get logged correctly - Zotero.logError(e); - throw e; + + // Use OS.File for files + if (source instanceof Components.interfaces.nsIFile) { + source = source.path; } - return deferred.promise; - } + else if (source.startsWith('^file:')) { + source = OS.Path.fromFileURI(source); + } + var options = { + encoding: charset ? charset : "utf-8" + }; + if (maxLength) { + options.bytes = maxLength; + } + return OS.File.read(source, options); + }); /** * Get the contents of a binary source asynchronously * - * @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read - * @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented) - * @return {Promise} A promise that is resolved with the contents of the source + * This is quite slow and should only be used in tests. + * + * @param {string path|nsIFile|file URI} source The source to read + * @param {Integer} [maxLength] Maximum length to fetch, in bytes + * @return {Promise} A promise for the contents of the source as a binary string */ - this.getBinaryContentsAsync = function (source, maxLength) { - if (typeof source == 'string') { - source = this.pathToFile(source); + this.getBinaryContentsAsync = Zotero.Promise.coroutine(function* (source, maxLength) { + // Use OS.File for files + if (source instanceof Components.interfaces.nsIFile) { + source = source.path; } - var deferred = Zotero.Promise.defer(); - NetUtil.asyncFetch(source, function(inputStream, status) { - if (!Components.isSuccessCode(status)) { - deferred.reject(new Components.Exception("Source read operation failed", status)); - return; - } - try { - var availableBytes = inputStream.available(); - deferred.resolve( - NetUtil.readInputStreamToString( - inputStream, - maxLength ? Math.min(maxLength, availableBytes) : availableBytes - ) - ); - } - catch (e) { - deferred.reject(e); - } - }); - return deferred.promise; - } + else if (source.startsWith('^file:')) { + source = OS.Path.fromFileURI(source); + } + var options = {}; + if (maxLength) { + options.bytes = maxLength; + } + var buf = yield OS.File.read(source, options); + return [...buf].map(x => String.fromCharCode(x)).join(""); + }); /* diff --git a/test/tests/fileTest.js b/test/tests/fileTest.js index e71a193b5..7236387a2 100644 --- a/test/tests/fileTest.js +++ b/test/tests/fileTest.js @@ -38,8 +38,43 @@ describe("Zotero.File", function () { assert.lengthOf(contents, 3); assert.equal(contents, "A\uFFFDB"); }) + + it("should respect maxLength", function* () { + var contents = yield Zotero.File.getContentsAsync( + OS.Path.join(getTestDataDirectory().path, "test.txt"), + false, + 6 + ); + assert.lengthOf(contents, 6); + assert.equal(contents, "Zotero"); + }); }) + describe("#getBinaryContentsAsync()", function () { + var magicPNG = ["89", "50", "4e", "47", "0d", "0a", "1a", "0a"].map(x => parseInt(x, 16)); + + it("should return a binary string", function* () { + var contents = yield Zotero.File.getBinaryContentsAsync( + OS.Path.join(getTestDataDirectory().path, "test.png") + ); + assert.isAbove(contents.length, magicPNG.length); + for (let i = 0; i < magicPNG.length; i++) { + assert.equal(magicPNG[i], contents.charCodeAt(i)); + } + }); + + it("should respect maxLength", function* () { + var contents = yield Zotero.File.getBinaryContentsAsync( + OS.Path.join(getTestDataDirectory().path, "test.png"), + magicPNG.length + ); + assert.lengthOf(contents, magicPNG.length) + for (let i = 0; i < contents.length; i++) { + assert.equal(magicPNG[i], contents.charCodeAt(i)); + } + }); + }); + describe("#copyDirectory()", function () { it("should copy all files within a directory", function* () { var tmpDir = Zotero.getTempDirectory().path; diff --git a/test/tests/fulltextTest.js b/test/tests/fulltextTest.js index 3f596a96f..32bf19bcc 100644 --- a/test/tests/fulltextTest.js +++ b/test/tests/fulltextTest.js @@ -109,10 +109,12 @@ describe("Zotero.Fulltext", function () { yield Zotero.Fulltext.downloadPDFTool('info', pdfToolsVersion); assert.ok(Zotero.Fulltext.pdfInfoIsRegistered()); + assert.equal( - (yield Zotero.File.getBinaryContentsAsync(cacheExecPath)), - (yield Zotero.File.getBinaryContentsAsync(execPath)) + (yield Zotero.Utilities.Internal.md5Async(cacheExecPath, false)), + (yield Zotero.Utilities.Internal.md5Async(execPath, false)) ); + if (!Zotero.isWin) { assert.equal((yield OS.File.stat(execPath)).unixMode, 0o755); }