/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is bookmark transaction code. * * The Initial Developer of the Original Code is * Joey Minta <jminta@gmail.com> * * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ function bookmarkTransactionManager() { this.wrappedJSObject = this; this.mTransactionManager = Components.classes["@mozilla.org/transactionmanager;1"] .createInstance(Components.interfaces.nsITransactionManager); this.mBatchCount = 0; this.classInfo = { getInterfaces: function (count) { var ifaces = [ Components.interfaces.nsISupports, Components.interfaces.nsIClassInfo ]; count.value = ifaces.length; return ifaces; }, getHelperForLanguage: function (language) { return null; }, contractID: "@mozilla.org/bookmarks/transactionManager;1", classDescription: "Booksmarks Transaction Manager", classID: Components.ID("{62d2f7fb-acd2-4876-aa2d-b607de9329ff}"), implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT, flags: 0 }; // Define our transactions function bkmkTxn() { this.item = null; this.parent = null; this.index = null; this.removedProp = null; }; bkmkTxn.prototype = { BMDS: null, QueryInterface: function bkmkTxnQI(iid) { if (!iid.equals(Components.interfaces.nsITransaction) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; }, merge : function (aTxn) {return false}, getHelperForLanguage: function (aCount) {return null}, getInterfaces : function (aCount) {return null}, canCreateWrapper : function (aIID) {return "AllAccess"}, mAssertProperties: function bkmkTxnAssertProps(aProps) { if (!aProps) { return; } for each (var prop in aProps) { for (var i = 0; i < this.Properties.length; i++) { var oldValue = this.BMDS.GetTarget(this.item, this.Properties[i], true); // must check, if paste call after copy the oldvalue didn't remove. if (!oldValue) { var newValue = aProps[i]; if (newValue) { this.BMDS.Assert(this.item, this.Properties[i], newValue, true); } } else { this.removedProp[i] = oldValue; } } } }, mUnassertProperties: function bkmkTxnUnassertProps(aProps) { if (!aProps) { return; } for each (var prop in aProps) { for (var i = 0; i < this.Properties.length; i++) { var oldValue = aProps[i]; if (oldValue) { this.BMDS.Unassert(this.item, this.Properties[i], oldValue); } } } } }; bkmkTxn.prototype.RDFC = Components.classes["@mozilla.org/rdf/container;1"] .createInstance(Components.interfaces.nsIRDFContainer); var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"] .getService(Components.interfaces.nsIRDFService); bkmkTxn.prototype.BMDS = rdfService.GetDataSource("rdf:bookmarks"); bkmkTxn.prototype.Properties = [rdfService.GetResource("http://home.netscape.com/NC-rdf#Name"), rdfService.GetResource("http://home.netscape.com/NC-rdf#URL"), rdfService.GetResource("http://home.netscape.com/NC-rdf#ShortcutURL"), rdfService.GetResource("http://home.netscape.com/NC-rdf#Description"), rdfService.GetResource("http://home.netscape.com/NC-rdf#WebPanel"), rdfService.GetResource("http://home.netscape.com/NC-rdf#FeedURL"), rdfService.GetResource("http://home.netscape.com/NC-rdf#MicsumGenURI"), rdfService.GetResource("http://home.netscape.com/NC-rdf#MicsumExpiration"), rdfService.GetResource("http://home.netscape.com/NC-rdf#GeneratedTitle")]; function bkmkInsertTxn(aAction) { this.type = "insert"; // move container declaration to here so it can be recognized if // undoTransaction is call after the BM manager is close and reopen. this.container = Components.classes["@mozilla.org/rdf/container;1"] .createInstance(Components.interfaces.nsIRDFContainer); } bkmkInsertTxn.prototype = { __proto__: bkmkTxn.prototype, isTransient: false, doTransaction: function bkmkInsertDoTxn() { this.RDFC.Init(this.BMDS, this.parent); // if the index is -1, we use appendElement, and then update the // index so that undoTransaction can still function if (this.index == -1) { this.RDFC.AppendElement(this.item); this.index = this.RDFC.GetCount(); } else { /*XXX- broken insert code, see bug 264571 try { this.RDFC.InsertElementAt(this.item, this.index, true); } catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) { // if this failed, then we assume that we really want to append, // because things are out of whack until we renumber. this.RDFC.AppendElement(this.item); // and then fix up the index so undo works this.index = this.RDFC.GetCount(); } */ this.RDFC.InsertElementAt(this.item, this.index, true); } // insert back all the properties this.mAssertProperties(this.removedProp); }, undoTransaction: function bkmkInsertUndoTxn() { // XXXvarga Can't use |RDFC| here because it's being "reused" elsewhere. this.container.Init(this.BMDS, this.parent); // remove all properties befor we remove the element so // nsLocalSearchService doesn't return deleted element in Search this.mUnassertProperties(this.removedProp); this.container.RemoveElementAt(this.index, true); }, redoTransaction: function bkmkInsertRedoTxn() { this.doTransaction(); } }; function bkmkRemoveTxn() { this.type = "remove"; } bkmkRemoveTxn.prototype = { __proto__: bkmkTxn.prototype, isTransient: false, doTransaction: function bkmkRemoveDoTxn() { this.RDFC.Init(this.BMDS, this.parent); // remove all properties befor we remove the element so // nsLocalSearchService doesn't return deleted element in Search this.mUnassertProperties(this.removedProp); this.RDFC.RemoveElementAt(this.index, false); }, undoTransaction: function bkmkRemoveUndoTxn() { this.RDFC.Init(this.BMDS, this.parent); this.RDFC.InsertElementAt(this.item, this.index, false); // insert back all the properties this.mAssertProperties(this.removedProp); }, redoTransaction: function () { this.doTransaction(); } } function bkmkImportTxn(aAction) { this.type = "import"; this.action = aAction; } bkmkImportTxn.prototype = { __proto__: bkmkTxn.prototype, isTransient: false, doTransaction: function bkmkImportDoTxn() {}, undoTransaction: function mkmkImportUndoTxn() { this.RDFC.Init(this.BMDS, this.parent); this.RDFC.RemoveElementAt(this.index, true); }, redoTransaction: function bkmkImportredoTxn() { this.RDFC.Init(this.BMDS, this.parent); this.RDFC.InsertElementAt(this.item, this.index, true); } }; this.BookmarkRemoveTransaction = bkmkRemoveTxn; this.BookmarkInsertTransaction = bkmkInsertTxn; this.BookmarkImportTransaction = bkmkImportTxn; } bookmarkTransactionManager.prototype.QueryInterface = function bkTxnMgrQI(aIID) { if (aIID.equals(Components.interfaces.nsISupports) || aIID.equals(Components.interfaces.nsIBookmarkTransactionManager)) { return this; } if (aIID.equals(Components.interfaces.nsIClassInfo)) { return this.classInfo; } throw NS_ERROR_NO_INTERFACE; } bookmarkTransactionManager.prototype.createAndCommitTxn = function bkmkTxnMgrCandC(aType, aAction, aItem, aIndex, aParent, aPropCount, aRemovedProps) { var txn; var nsIBookmarkTransactionManager = Components.interfaces.nsIBookmarkTransactionManager; switch (aType) { case nsIBookmarkTransactionManager.IMPORT: txn = new this.BookmarkImportTransaction(aAction); break; case nsIBookmarkTransactionManager.INSERT: txn = new this.BookmarkInsertTransaction(aAction); break; case nsIBookmarkTransactionManager.REMOVE: txn = new this.BookmarkRemoveTransaction(aAction); break; default: Components.utils.reportError("Unknown bookmark transaction type:"+aType); throw NS_ERROR_FAILURE; } txn.item = aItem; txn.parent = aParent; txn.index = aIndex; txn.removedProp = aRemovedProps; txn.action = aAction; txn.wrappedJSObject = txn; this.mTransactionManager.doTransaction(txn); } bookmarkTransactionManager.prototype.startBatch = function bkmkTxnMgrUndo() { if (this.mBatchCount == 0) { this.mTransactionManager.beginBatch(); } this.mBatchCount++; } bookmarkTransactionManager.prototype.endBatch = function bkmkTxnMgrUndo() { this.mBatchCount--; if (this.mBatchCount == 0) { this.mTransactionManager.endBatch(); } } bookmarkTransactionManager.prototype.undo = function bkmkTxnMgrUndo() { this.mTransactionManager.undoTransaction(); } bookmarkTransactionManager.prototype.redo = function bkmkTxnMgrRedo() { this.mTransactionManager.redoTransaction(); } bookmarkTransactionManager.prototype.canUndo = function bkmkTxnMgrCanUndo() { return this.mTransactionManager.numberOfUndoItems > 0; } bookmarkTransactionManager.prototype.canRedo = function bkmkTxnMgrCanRedo() { return this.mTransactionManager.numberOfRedoItems > 0; } bookmarkTransactionManager.prototype.__defineGetter__("transactionManager", function bkmkTxnMgrGetter() { return this.mTransactionManager; }); /**** **** module registration ****/ const kFactory = { createInstance: function (outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new bookmarkTransactionManager()).QueryInterface(iid); } }; var bkmkTxnMgrModule = { mCID: Components.ID("{8be133d0-681d-4f0b-972b-6a68e41afb62}"), mContractID: "@mozilla.org/bookmarks/transactionmanager;1", registerSelf: function (compMgr, fileSpec, location, type) { compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); compMgr.registerFactoryLocation(this.mCID, "Bookmark Transaction Manager", this.mContractID, fileSpec, location, type); }, getClassObject: function (compMgr, cid, iid) { if (!cid.equals(this.mCID)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; return kFactory; }, canUnload: function(compMgr) { return true; } }; function NSGetModule(compMgr, fileSpec) { return bkmkTxnMgrModule; }