From a745cde2cfd6c85520e59eff04bd39bc269cf9c0 Mon Sep 17 00:00:00 2001 From: Tom Najdek Date: Sat, 2 Oct 2021 00:34:51 +0200 Subject: [PATCH] New implementation of a download function #2216 This resolves a problem where, in certain scenarios, Zotero.file.download throws an exception even though file is successfully downloaded. Furthermore this new download function should be more memory-efficient, improving performance when dealing with large files. --- chrome/content/zotero/xpcom/file.js | 70 ++++++++++++++++++----------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index 57320b2502..4e8a3031bb 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -445,9 +445,8 @@ Zotero.File = new function(){ }); }); }; - - - this.download = Zotero.Promise.coroutine(function* (uri, path) { + + this.download = async function (uri, path) { var uriStr = uri.spec || uri; Zotero.debug(`Saving ${uriStr} to ${path.pathQueryRef || path}`); @@ -459,31 +458,52 @@ Zotero.File = new function(){ } var deferred = Zotero.Promise.defer(); - NetUtil.asyncFetch(uri, function (is, status, request) { - if (!Components.isSuccessCode(status)) { - Zotero.logError(status); - let msg = Zotero.getString('sync.error.checkConnection'); - switch (status) { - case 2152398878: - // TODO: Localize - msg = "Server not found. Check your internet connection." - break; + const uri_ = NetUtil.ioService.newURI(uri); + const inputChannel = NetUtil.ioService.newChannelFromURI(uri_); + const outputChannel = FileUtils.openSafeFileOutputStream(new FileUtils.File(path)); + const pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, 0xffffffff, null); + + let listener = Cc[ + "@mozilla.org/network/simple-stream-listener;1" + ].createInstance(Ci.nsISimpleStreamListener); + + listener.init(pipe.outputStream, { + onStartRequest(request) { + // NOTE: This noop callback is required, do not remove. + }, + onStopRequest(request, status) { + const responseStatus = 'responseStatus' in request ? request.responseStatus : null; + pipe.outputStream.close(); + + if (!Components.isSuccessCode(status)) { + Zotero.logError(status); + let msg = Zotero.getString('sync.error.checkConnection'); + switch (status) { + case 2152398878: + // TODO: Localize + msg = "Server not found. Check your internet connection." + break; + } + deferred.reject(new Error(msg)); + return; + } + if (responseStatus != 200) { + let msg = `Download failed with response code ${responseStatus}`; + Zotero.logError(msg); + deferred.reject(new Error(msg)); + return; } - deferred.reject(new Error(msg)); - return; } - if (request.responseStatus != 200) { - let msg = `Download failed with response code ${request.responseStatus}`; - Zotero.logError(msg); - deferred.reject(new Error(msg)); - return; - } - deferred.resolve(is); }); - var is = yield deferred.promise; - yield Zotero.File.putContentsAsync(path, is); - }); - + + NetUtil.asyncCopy(pipe.inputStream, outputChannel, function(aResult) { + deferred.resolve(); + }); + inputChannel.asyncOpen(listener, null); + + return deferred.promise; + }; /** * Rename file within its parent directory