// XXX temporary hack if(!("Cc" in window)) window.Cc = Components.classes; if(!("Ci" in window)) window.Ci = Components.interfaces; function d(s) { dump("-----> " + s + "\n"); } /** * Create a closure that will call the provided method on the provided object * with the scope of the provided object. Inspired from the brilliant GM_Hitch */ function XPCL_CreateListener(object, methodName) { return function(event) { return object[methodName].apply(object, [event]); } } var XpclToolbarController = {}; // currentUrlId XpclToolbarController.QueryInterface = function(aIID) { if(aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsISupports)) { return this; } throw Components.results.NS_ERROR_NO_INTERFACE; } XpclToolbarController.addCurrentDocument = function() { var writer = null; try { var currentBrowser = gBrowser.selectedBrowser; var currentDocument = currentBrowser.contentWindow.document; if(!currentDocument) { d("addCurrentDocument called with null document"); return; } // Don't try to index search result pages var uri = currentBrowser.currentURI; if(uri.schemeIs("lucene")) { return; } // If the current document is already in the index, remove it first if(this.currentUrlId) { this.deleteDocument(this.currentUrlId); } var content = this.luceneService.filterHTMLDocument(currentDocument.body); var tag = this.tagsTextBox.value; var title = currentDocument.title; var link = uri.spec; var author = uri.host; var updated = this.luceneService.formatIndexDateGMT((new Date()).getTime() * 1000); var analyzer = this.luceneFactory.getAnalyzer(0); /* * If an error is returned, it is a new index. Call getIndexWriter with * create = true */ try { writer = this.luceneFactory.getIndexWriter(this.index, analyzer, false, true); } catch(e) { writer = this.luceneFactory.getIndexWriter(this.index, analyzer, true, true); } var doc = this.luceneFactory.createDocument(); doc.add(this.luceneFactory.createTextStringField("content", content, true)); doc.add(this.luceneFactory.createTextStringField("tag", tag, true)); doc.add(this.luceneFactory.createTextStringField("link", link, true)); doc.add(this.luceneFactory.createTextStringField("title", title, true)); doc.add(this.luceneFactory.createTextStringField("author", author, true)); doc.add(this.luceneFactory.createTextKeywordField("updated", updated)); doc.add(this.luceneFactory.createTextKeywordField("key_link", link)); writer.addDocument(doc); writer.optimize(); this.lastAddTags = tag; d("Added document, tag: " + tag + ", title: " + title + ", link: " + link + ", author: " + author + ", updated: " + updated + ", content: " + content); } finally { if(writer) { writer.close(); } } } XpclToolbarController.deleteDocument = function(id) { var reader = null; try { reader = this.luceneFactory.getIndexReader(this.index); reader.deleteDoc(id); reader.commit(); d("Deleted document with id = " + id); return true; } catch(e) { Components.utils.reportError(e); } finally { if(reader) { reader.close(); } } return false; } XpclToolbarController.updateInterface = function() { var uri = gBrowser.selectedBrowser.currentURI; d("XpclToolbarController.updateInterface, uri=" + (uri ? uri.spec : "null")); if(uri && (uri.schemeIs("http") || uri.schemeIs("https"))) { // TODO: || uri.schemeIs("file"), figure out how to trick checkloaduri policy this.loadCurrentUrlInfo(uri.spec); if(this.currentUrlId != null) { // Current document exists, set the tags and enable all buttons var tags = this.currentUrlTags ? this.currentUrlTags : this.lastAddTags; this.updateUI({ "add-disabled" : false, "delete-disabled" : false, "tags-disabled" : false, "tags-value" : tags }); } else { // Not in index, disable delete. this.updateUI({ "add-disabled" : false, "delete-disabled" : true, "tags-disabled" : false, "tags-value" : this.lastAddTags }); } return; } /* * if the URI is null or is any other URL type (about:blank, lucene:, etc) * disable the buttons */ this.updateUI({ "add-disabled" : true, "delete-disabled" : true, "tags-disabled" : true, "tags-value" : this.lastAddTags }); } XpclToolbarController.updateUI = function(state) { if(this.addButton) this.addButton.disabled = state["add-disabled"]; if(this.deleteButton) this.deleteButton.disabled = state["delete-disabled"]; if(this.tagsTextBox) { this.tagsTextBox.disabled = state["tags-disabled"]; this.tagsTextBox.value = state["tags-value"]; } } XpclToolbarController.loadCurrentUrlInfo = function(url) { var reader = null; this.currentUrlTags = null; this.currentUrlId = null; try { var reader = this.luceneFactory.getIndexReader(this.index); var query = this.luceneFactory.createSingleTermQuery("key_link", url); var searcher = this.luceneFactory.getIndexSearcher(reader); var hits = searcher.search(query); if(hits.length() > 0) { this.currentUrlId = hits.id(0); var doc = hits.doc(0); var tags = doc.get("tag"); if(tags != null) { this.currentUrlTags = tags; } } d("loadCurrentInfo url = " + url + " id = " + this.currentUrlId + " tags = " + this.currentUrlTags); } catch(e) { if(e.result == Ci.nsILuceneFactory.XPCL_ERR_IO) { /* * If this exception thrown was because of a missing segments file, * that's OK since that just means this is an empty index. */ try { var segments = this.index.clone(); segments.append("segments"); if(!segments.exists()) { return; } } catch(eSeg) { // fall through } } /* * If we get here, either the segments file existed and the error was * thrown for another reason, or there was an error checking for the file. * Log the error and return. */ Components.utils.reportError(e); } finally { if(reader) { reader.close(); } } } XpclToolbarController.tabChange = function(event) { // Update the interface when the user switches tabs this.updateInterface(); } XpclToolbarController.addButtonCommand = function(event) { try { window.setCursor("wait"); this.addCurrentDocument(); this.updateInterface(); } finally { window.setCursor("auto"); } } XpclToolbarController.deleteButtonCommand = function(event) { try { window.setCursor("wait"); if(this.currentUrlId != null) { this.deleteDocument(this.currentUrlId); this.updateInterface(); } } finally { window.setCursor("auto"); } } // nsIWebProgressListener XpclToolbarController.onLocationChange = function(aWebProgress, aRequest, aLocation) { d("XpclToolbarController.onLocationChange, aLocation = " + aLocation.spec); } XpclToolbarController.onStateChange = function(aWebProgress, aRequest, aStateFlags, aStatus) { d("XpclToolbarController.onStateChange"); // Update the interface when a new location has finished loading if(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) { this.updateInterface(); } } XpclToolbarController.onProgressChange = function(aWebprogess, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { } XpclToolbarController.onStatusChange = function(aWebProgess, aRequest, aStatus, aMessage) { } XpclToolbarController.onSecurityChange = function(aWebProgress, aRequest, aState) { } // Chrome window event handlers XpclToolbarController.chromeLoad = function(event) { d("XpclToolbarController.chromeLoad"); // Chrome is ready, stash important elements in member variables this.tagsTextBox = document.getElementById("xpclTags"); this.addButton = document.getElementById("xpclButtonAdd"); this.deleteButton = document.getElementById("xpclButtonDelete"); // Listen for events sent from the content gBrowser.addEventListener("xpclucene-delete", this.contentDeleteListener, false, true); gBrowser.tabContainer .addEventListener("select", this.tabChangeListener, false); gBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_STATUS); this.updateInterface(); } XpclToolbarController.chromeUnload = function(event) { d("XpclToolbarController.chromeUnload"); gBrowser.removeProgressListener(this); this.cleanup(); } /** * Handles event dispatched on our search results page when user clicks a * "delete" link. */ XpclToolbarController.contentDelete = function(event) { // Mare sure the event is coming from a search results page if(gBrowser.currentURI.schemeIs("lucene")) { var div = event.target; if(this.deleteDocument(div.id)) { div.parentNode.removeChild(div); // TODO: Do something smart when the last result is removed } } } XpclToolbarController.init = function() { this.luceneService = Cc["@skrul.com/xpclucene-service;1"] .getService(Ci.nsILuceneService); this.luceneFactory = Cc["@skrul.com/xpclucene-factory;1"] .getService(Ci.nsILuceneFactory); this.index = this.luceneService.indexLocation("default"); // Define event listeners this.chromeLoadListener = XPCL_CreateListener(this, "chromeLoad"); this.chromeUnloadListener = XPCL_CreateListener(this, "chromeUnload"); this.tabChangeListener = XPCL_CreateListener(this, "tabChange"); this.contentDeleteListener = XPCL_CreateListener(this, "contentDelete"); // Store the index state of the current document this.currentUrlTags = null; this.currentUrlId = null; this.lastAddTags = ""; d("XpclToolbarController.init"); window.addEventListener("load", this.chromeLoadListener, false); window.addEventListener("unload", this.chromeUnloadListener, false); } XpclToolbarController.cleanup = function() { gBrowser.tabContainer. removeEventListener("select", this.tabChangeListener, false); window.removeEventListener("load", this.chromeLoadListener, false); window.removeEventListener("unload", this.chromeUnloadListener, false); gBrowser.removeEventListener("xpclucene-delete", this.contentDeleteListener, false); this.luceneService = null; this.luceneFactory = null; } try { XpclToolbarController.init(); } catch(e) { Components.utils.reportError(e); }