From be5ddb1505af1bd8468fe009c79475f8810d3e32 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 5 Dec 2006 23:23:01 +0000 Subject: [PATCH] Add support for taking snapshots of files handled by plugins (e.g. Flash, QuickTime, PDFs) with importDocument() Apparently nsIWebBrowserPersist's nsIWebProgressListener never reaches STOP_STOP and STATE_IS_NETWORK in onStateChange() for plugin-handled files, so the import process wasn't completing. I'm now using onProgressChange() for such files. Also: - Moved DB commits out of callbacks so that transactions aren't left open if the progress listener never completes - Blocked attempts to call snapshots on plugin files with file:// URLs, since they're one case where the listener doesn't work properly - Added Zotero.MIME.hasNativeHandler() to be able to distinguish between natively handled files and plugin-handled files. - Fixed a couple bugs that might have made prevented snapshots from working properly --- chrome/content/zotero/xpcom/attachments.js | 77 ++++++++++++++++++---- chrome/content/zotero/xpcom/mime.js | 23 ++++++- chrome/content/zotero/xpcom/zotero.js | 50 +++++++++++--- 3 files changed, 125 insertions(+), 25 deletions(-) diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 2727766708..f908c1cd5f 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -224,12 +224,36 @@ Zotero.Attachments = new function(){ file.append(fileName); wbp.progressListener = new Zotero.WebProgressFinishListener(function(){ - _addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL, - mimeType, null, sourceItemID, itemID); - - Zotero.DB.commitTransaction(); + try { + _addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL, + mimeType, null, sourceItemID, itemID); + } + catch (e) { + // Clean up + if (itemID) { + var item = Zotero.Items.get(itemID); + if (item) { + item.erase(); + } + + try { + var destDir = Zotero.getStorageDirectory(); + destDir.append(itemID); + if (destDir.exists()) { + destDir.remove(true); + } + } + catch (e) {} + } + + throw (e); + } }); + // The attachment is still incomplete here, but we can't risk + // leaving the transaction open if the callback never triggers + Zotero.DB.commitTransaction(); + wbp.saveURI(nsIURL, null, null, null, null, file); } catch (e){ @@ -282,7 +306,7 @@ Zotero.Attachments = new function(){ function linkFromDocument(document, sourceItemID, parentCollectionIDs){ Zotero.debug('Linking attachment from document'); - var url = document.location; + var url = document.location.href; var title = document.title; // TODO: don't use Mozilla-generated title for images, etc. var mimeType = document.contentType; var charsetID = Zotero.CharacterSets.getID(document.characterSet); @@ -312,10 +336,20 @@ Zotero.Attachments = new function(){ function importFromDocument(document, sourceItemID, forceTitle, parentCollectionIDs){ Zotero.debug('Importing attachment from document'); - var url = document.location; + var url = document.location.href; var title = forceTitle ? forceTitle : document.title; var mimeType = document.contentType; var charsetID = Zotero.CharacterSets.getID(document.characterSet); + var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, _getExtensionFromURL(url)) + + // TODO: make this work -- with local plugin files, onStateChange in the + // nsIWebBrowserPersist's nsIWebProgressListener never completes and + // onProgressChange returns -1 for maxTotal, which prevents it from + // triggering the callback. + if (!hasNativeHandler && url.substr(0, 4) == 'file') { + Zotero.debug('Import of loaded files from plugins is not supported'); + return false; + } const nsIWBP = Components.interfaces.nsIWebBrowserPersist; var wbp = Components @@ -360,6 +394,8 @@ Zotero.Attachments = new function(){ wbp.progressListener = new Zotero.WebProgressFinishListener(function(){ try { + Zotero.DB.beginTransaction(); + _addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL, mimeType, charsetID, sourceItemID, itemID); @@ -377,23 +413,32 @@ Zotero.Attachments = new function(){ catch (e) { Zotero.DB.rollbackTransaction(); - try { - // Clean up - if (itemID) { + // Clean up + if (itemID) { + var item = Zotero.Items.get(itemID); + if (item) { + item.erase(); + } + + try { var destDir = Zotero.getStorageDirectory(); destDir.append(itemID); if (destDir.exists()) { destDir.remove(true); } } + catch (e) {} } - catch (e) {} throw (e); } Zotero.Fulltext.indexDocument(document, itemID); - }); + }, !hasNativeHandler); + + // The attachment is still incomplete here, but we can't risk + // leaving the transaction open if the callback never triggers + Zotero.DB.commitTransaction(); wbp.saveDocument(document, file, destDir, mimeType, encodingFlags, false); } @@ -430,7 +475,7 @@ Zotero.Attachments = new function(){ try { var ext = Components.classes["@mozilla.org/mime;1"] .getService(Components.interfaces.nsIMIMEService) - .getPrimaryExtension(mimeType, nsIURL.fileExt ? nsIURL.fileExt : null); + .getPrimaryExtension(mimeType, nsIURL.fileExtension); } // getPrimaryExtension doesn't work on Linux catch (e) {} @@ -440,6 +485,14 @@ Zotero.Attachments = new function(){ } + function _getExtensionFromURL(url) { + var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] + .createInstance(Components.interfaces.nsIURL); + nsIURL.spec = url; + return nsIURL.fileExtension; + } + + /** * Create a new item of type 'attachment' and add to the itemAttachments table * diff --git a/chrome/content/zotero/xpcom/mime.js b/chrome/content/zotero/xpcom/mime.js index a6951ad83b..231994d61c 100644 --- a/chrome/content/zotero/xpcom/mime.js +++ b/chrome/content/zotero/xpcom/mime.js @@ -26,6 +26,7 @@ Zotero.MIME = new function(){ this.sniffForBinary = sniffForBinary; this.getMIMETypeFromData = getMIMETypeFromData; this.getMIMETypeFromFile = getMIMETypeFromFile; + this.hasNativeHandler = hasNativeHandler; this.hasInternalHandler = hasInternalHandler; this.fileHasInternalHandler = fileHasInternalHandler; @@ -134,8 +135,8 @@ Zotero.MIME = new function(){ /* - * Determine if a MIME type can be handled internally (natively or with plugins) - * or if it needs to be passed off to an external helper app + * Determine if a MIME type can be handled natively + * or if it needs to be passed off to a plugin or external helper app * * ext is an optional extension hint (only needed for text/plain files * that should be forced to open externally) @@ -146,7 +147,7 @@ Zotero.MIME = new function(){ * Note: nsIMIMEInfo provides a hasDefaultHandler() method, but it doesn't * do what we need */ - function hasInternalHandler(mimeType, ext){ + function hasNativeHandler(mimeType, ext) { if (mimeType=='text/plain'){ if (this.isExternalTextExtension(ext)){ Zotero.debug('text/plain file has extension that should be handled externally'); @@ -160,6 +161,22 @@ Zotero.MIME = new function(){ return true; } + return null; + } + + + /* + * Determine if a MIME type can be handled internally + * or if it needs to be passed off to an external helper app + * + * Similar to hasNativeHandler() but also includes plugins + */ + function hasInternalHandler(mimeType, ext) { + var isNative = hasNativeHandler(mimeType, ext); + if (isNative !== null) { + return isNative; + } + // Is there a better way to get to navigator? var types = Components.classes["@mozilla.org/appshell/appShellService;1"] .getService(Components.interfaces.nsIAppShellService) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 7246752eff..0d630862a1 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1148,16 +1148,46 @@ Zotero.Browser = new function() { } -Zotero.WebProgressFinishListener = function(onFinish){ - this.onStateChange = function(wp, req, stateFlags, status){ - if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) - && (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)){ - onFinish(); +/* + * Implements nsIWebProgressListener + * + * For plugin content, onStateChange doesn't seem to be called after the document + * finishes loading, so the useProgress flag can be used to run onFinish() + * when all the content of the request has been loaded -- this should only be + * used for single file requests (generally, things handled by plugins) + */ +Zotero.WebProgressFinishListener = function(onFinish, useProgress, wbp) { + var _finished = false; + + this.onStateChange = function(wp, req, stateFlags, status) { + if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) + && (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)) { + if (useProgress) { + Zotero.debug('WebProgressFinishListener: useProgress set but STOP_STOP and STATE_IS_NETWORK were reached', 2); + if (_finished) { + return; + } + else { + _finished = true; + } } + onFinish(); } - - this.onLocationChange = function(){} - this.onProgressChange = function(){} - this.onSecurityChange = function(){} - this.onStatusChange = function(){} + } + + this.onLocationChange = function() {} + + this.onProgressChange = function(wp, req, cur, max, curTotal, maxTotal) { + // DEBUG: This will never complete if the file size (maxTotal) isn't + // available, which seems to be the case with local files and is + // presumably the case with remote servers that don't send the file size. + //Zotero.debug('Current total: ' + curTotal + ' Max total: ' + maxTotal); + if (!_finished && useProgress && (curTotal == maxTotal)) { + _finished = true; + onFinish(); + } + } + + this.onSecurityChange = function() {} + this.onStatusChange = function(wp, req, st, msg) {} } \ No newline at end of file