Fix bug 412914 - Enable offline caching for ical calendar with many events -> startup horrible slow. r=philipp
authorPhilippe Martinak <philippe.martinak@i-carre.net>
Tue, 17 Jan 2012 16:16:54 +0100
changeset 10439 45bca33cdcfdf1d06dbb7dce497e3e623a550526
parent 10438 221b010fd6a1b3e60ea935449d2abd58c5e68e38
child 10440 986abadfbeb759e5de8f81397f67f90bb3c6471c
push idunknown
push userunknown
push dateunknown
reviewersphilipp
bugs412914
Fix bug 412914 - Enable offline caching for ical calendar with many events -> startup horrible slow. r=philipp
calendar/providers/storage/calStorageCalendar.js
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -418,28 +418,37 @@ calStorageCalendar.prototype = {
         try {
             aStmt.params.cal_id = this.id;
             this.mLastStatement = aStmt;
         } catch (e) {
             this.logError("prepareStatement exception", e);
         }
     },
 
+    prepareBatchTransaction: function cSC_prepareBatchTransaction() {
+        if (this.mBatchCount > 0 && !this.mDB.transactionInProgress) {
+            this.acquireTransaction();
+        }
+        return this.mBatchCount < 1;
+    },
+
     refresh: function cSC_refresh() {
         // no-op
     },
 
     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
     addItem: function cSC_addItem(aItem, aListener) {
         let newItem = aItem.clone();
         return this.adoptItem(newItem, aListener);
     },
 
     // void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
     adoptItem: function cSC_adoptItem(aItem, aListener) {
+        let transact = this.prepareBatchTransaction();
+
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.ADD,
                                          null,
                                          "Calendar is readonly");
             return;
         }
@@ -447,17 +456,17 @@ calStorageCalendar.prototype = {
         if (aItem.id == null) {
             // is this an error?  Or should we generate an IID?
             aItem.id = cal.getUUID();
         } else {
             var olditem = this.getItemById(aItem.id);
             if (olditem) {
                 if (this.relaxedMode) {
                     // we possibly want to interact with the user before deleting
-                    this.deleteItemById(aItem.id);
+                    this.deleteItemById(aItem.id, transact);
                 } else {
                     this.notifyOperationComplete(aListener,
                                                  Components.interfaces.calIErrors.DUPLICATE_ID,
                                                  Components.interfaces.calIOperationListener.ADD,
                                                  aItem.id,
                                                  "ID already exists for addItem");
                     return;
                 }
@@ -467,17 +476,17 @@ calStorageCalendar.prototype = {
         let parentItem = aItem.parentItem;
         if (parentItem != aItem) {
             parentItem = parentItem.clone();
             parentItem.recurrenceInfo.modifyException(aItem, true);
         }
         parentItem.calendar = this.superCalendar;
         parentItem.makeImmutable();
 
-        this.flushItem(parentItem, null);
+        this.flushItem(parentItem, null, transact);
 
         // notify the listener
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.ADD,
                                      aItem.id,
                                      aItem);
 
@@ -498,17 +507,19 @@ calStorageCalendar.prototype = {
             onOperationComplete: function (this_, status, opType, id, offlineFlag) {
                 this_.doModifyItem(aNewItem, aOldItem, aListener, offlineFlag);
             }
         };
         this.getItemOfflineFlag(aOldItem, offlineJournalFlagListener);
     },
 
     doModifyItem: function cSC_doModifyItem(aNewItem, aOldItem, aListener, offlineFlag) {
+        let transact = this.prepareBatchTransaction();
         let oldOfflineFlag = offlineFlag;
+
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.MODIFY,
                                          null,
                                          "Calendar is readonly");
             return null;
         }
@@ -570,32 +581,34 @@ calStorageCalendar.prototype = {
             if (aOldItem.generation == modifiedItem.generation) { // has been cloned and modified
                 // Only take care of incrementing the generation if relaxed mode is
                 // off. Users of relaxed mode need to take care of this themselves.
                 modifiedItem.generation += 1;
             }
         }
 
         modifiedItem.makeImmutable();
-        this.flushItem (modifiedItem, aOldItem);
+        this.flushItem (modifiedItem, aOldItem, transact);
         this.setOfflineJournalFlag(aNewItem, oldOfflineFlag);
 
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.MODIFY,
                                      modifiedItem.id,
                                      modifiedItem);
 
         // notify observers
         this.observers.notify("onModifyItem", [modifiedItem, aOldItem]);
         return null;
     },
 
     // void deleteItem( in string id, in calIOperationListener aListener );
     deleteItem: function cSC_deleteItem(aItem, aListener) {
+        let transact = this.prepareBatchTransaction();
+
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.DELETE,
                                          null,
                                          "Calendar is readonly");
             return;
         }
@@ -610,17 +623,17 @@ calStorageCalendar.prototype = {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.DELETE,
                                          null,
                                          "ID is null for deleteItem");
             return;
         }
 
-        this.deleteItemById(aItem.id);
+        this.deleteItemById(aItem.id, transact);
 
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.DELETE,
                                      aItem.id,
                                      aItem);
 
         // notify observers
@@ -2049,28 +2062,28 @@ calStorageCalendar.prototype = {
                 params[entryname + "_tz"] = tz.icalComponent.serializeToICS();
             }
         } else {
             params[entryname] = null;
             params[entryname + "_tz"] = null;
         }
     },
 
-    flushItem: function cSC_flushItem(item, olditem) {
+    flushItem: function cSC_flushItem(item, olditem, transact) {
         ASSERT(!item.recurrenceId, "no parent item passed!", true);
 
         try {
-            this.deleteItemById(olditem ? olditem.id : item.id);
-            this.acquireTransaction();
+            this.deleteItemById(olditem ? olditem.id : item.id, transact);
+            if (transact) this.acquireTransaction();
             this.writeItem(item, olditem);
         } catch (e) {
-            this.releaseTransaction(e);
+            if (transact) this.releaseTransaction(e);
             throw e;
         }
-        this.releaseTransaction();
+        if (transact) this.releaseTransaction();
 
         this.cacheItem(item);
     },
 
     //
     // The write* functions execute the database bits
     // to write the given item type.  They're to return
     // any bits they want or'd into flags, which will be passed
@@ -2441,31 +2454,32 @@ calStorageCalendar.prototype = {
         return CAL_ITEM_FLAG.HAS_ALARMS;
     },
 
     /**
      * Deletes the item with the given item id.
      *
      * @param aID           The id of the item to delete.
      */
-    deleteItemById: function cSC_deleteItemById(aID) {
-        this.acquireTransaction();
+    deleteItemById: function cSC_deleteItemById(aID, transact) {
+        if (transact) this.acquireTransaction();
+
         try {
             this.mDeleteAttendees(aID, this.id);
             this.mDeleteProperties(aID, this.id);
             this.mDeleteRecurrence(aID, this.id);
             this.mDeleteEvent(aID, this.id);
             this.mDeleteTodo(aID, this.id);
             this.mDeleteAttachments(aID, this.id);
             this.mDeleteRelations(aID, this.id);
             this.mDeleteMetaData(aID, this.id);
             //this.mDeleteAllMetaData(aID, this.id);
             this.mDeleteAlarms(aID, this.id);
         } catch (e) {
-            this.releaseTransaction(e);
+            if (transact) this.releaseTransaction(e);
             throw e;
         }
         this.releaseTransaction();
 
         delete this.mItemCache[aID];
         delete this.mRecEventCache[aID];
         delete this.mRecTodoCache[aID];
     },
@@ -2492,18 +2506,19 @@ calStorageCalendar.prototype = {
         }
     },
 
     //
     // calISyncWriteCalendar interface
     //
 
     setMetaData: function cSC_setMetaData(id, value) {
+        this.prepareBatchTransaction();
+        this.mDeleteMetaData(id, this.id);
 
-        this.mDeleteMetaData(id, this.id);
         try {
             this.prepareStatement(this.mInsertMetaData);
             var sp = this.mInsertMetaData.params;
             sp.item_id = id;
             sp.value = value;
             this.mInsertMetaData.execute();
         } catch (e) {
             // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
@@ -2514,16 +2529,17 @@ calStorageCalendar.prototype = {
                 throw e;
             }
         } finally {
             this.mInsertMetaData.reset();
         }
     },
 
     deleteMetaData: function cSC_deleteMetaData(id) {
+        this.prepareBatchTransaction();
         this.mDeleteMetaData(id, this.id);
     },
 
     getMetaData: function cSC_getMetaData(id) {
         let query = this.mSelectMetaData;
         let value = null;
         try {
             this.prepareStatement(query);
@@ -2557,16 +2573,17 @@ calStorageCalendar.prototype = {
             out_ids.value = ids;
             out_values.value = values;
         } catch (e) {
             this.logError("Error getting all metadata!", e);
         } finally {
             query.reset();
         }
     },
+
     /**
      * Internal logging function that should be called on any database error,
      * it will log as much info as possible about the database context and
      * last statement so the problem can be investigated more easilly.
      *
      * @param message           Error message to log.
      * @param exception         Exception that caused the error.
      */