Fix bug 380060 - Implementation of full offline mode (esp. modification of calendar data). r=philipp,a=philipp
authorMohit Kanwal <mohit.kanwal@gmail.com>
Tue, 18 Oct 2011 00:52:45 +0200
changeset 9290 c877359b9075a55c0d1484b6a0c6efd071b69839
parent 9289 cab170bfadec5b619b7b04704d73d6b2c433283e
child 9291 5b49e89846ae7937827e964f8c6100c65b61bd9e
push id230
push userbugzilla@standard8.plus.com
push dateTue, 08 Nov 2011 22:55:24 +0000
treeherdercomm-beta@63dad5648415 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp, philipp
bugs380060
Fix bug 380060 - Implementation of full offline mode (esp. modification of calendar data). r=philipp,a=philipp
calendar/base/content/calendar-common-sets.js
calendar/base/content/dialogs/calendar-conflicts-dialog.xul
calendar/base/jar.mn
calendar/base/modules/calProviderUtils.jsm
calendar/base/public/calICalendar.idl
calendar/base/public/calIChangeLog.idl
calendar/base/src/calCachedCalendar.js
calendar/base/src/calUtils.js
calendar/providers/caldav/calDavCalendar.js
calendar/providers/caldav/calDavRequestHandlers.js
calendar/providers/ics/calICSCalendar.js
calendar/providers/memory/calMemoryCalendar.js
calendar/providers/storage/calStorageCalendar.js
calendar/providers/storage/calStorageUpgrade.jsm
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -473,17 +473,18 @@ var calendarController = {
         let selected_events_requires_network = 0;
         let selected_events_invitation = 0;
 
         if (selLength > 0) {
             for each (var item in selectedItems) {
                 if (item.calendar.readOnly) {
                     selected_events_readonly++;
                 }
-                if (item.calendar.getProperty("requiresNetwork")) {
+                if (item.calendar.getProperty("requiresNetwork") &&
+                    !item.calendar.getProperty("cache.enabled")) {
                     selected_events_requires_network++;
                 }
 
                 if (cal.isInvitation(item)) {
                     selected_events_invitation++;
                 } else if (item.organizer) {
                     // If we are the organizer and there are attendees, then
                     // this is likely also an invitation.
@@ -521,18 +522,20 @@ var calendarController = {
     selected_events_invitation: false,
 
     /**
      * Returns a boolean indicating if its possible to write items to any
      * calendar.
      */
     get writable() {
         return !this.all_readonly &&
-               (!this.offline || (this.has_local_calendars &&
-               !this.all_local_calendars_readonly));
+               (!this.offline ||
+                this.has_cached_calendars ||
+                (this.has_local_calendars &&
+                 !this.all_local_calendars_readonly));
     },
 
     /**
      * Returns a boolean indicating if the application is currently in offline
      * mode.
      */
     get offline() {
         return getIOService().offline;
@@ -558,16 +561,31 @@ var calendarController = {
      * network access.
      */
     get has_local_calendars() {
         var calMgr = getCalendarManager();
         return (calMgr.networkCalendarCount < calMgr.calendarCount);
     },
 
     /**
+     * Returns a boolean indicating if there are cached calendars and thus that don't require
+     * network access.
+     */
+    get has_cached_calendars() {
+        let calMgr = getCalendarManager();
+        let calendars = calMgr.getCalendars({});
+        for each (let calendar in calendars) {
+            if (calendar.getProperty("cache.enabled")) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    /**
      * Returns a boolean indicating that there is only one calendar left.
      */
     get last_calendar() {
         return (getCalendarManager().calendarCount < 2);
     },
 
     /**
      * Returns a boolean indicating that all local calendars are readonly
new file mode 100644
--- /dev/null
+++ b/calendar/base/content/dialogs/calendar-conflicts-dialog.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- ***** 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 Mozilla Calendar code.
+   -
+   - The Initial Developer of the Original Code is
+   -   Philipp Kewisch <mozilla@kewis.ch>
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - 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 ***** -->
+
+<?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-views.css"?>
+
+<dialog id="calendar-conflicts-dialog"
+        windowtype="Calendar:Conflicts"
+        onload="onLoad()"
+        ondialogaccept="return onAccept();"
+        ondialogcancel="return onCancel();"
+        persist="screenX screenY"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://calendar/content/mouseoverPreviews.js"/>
+  <script type="application/javascript" src="chrome://calendar/content/calUtils.js"/>
+  <script type="application/javascript"><![CDATA[
+    Components.utils.import("resource://calendar/modules/calUtils.jsm");
+    function onLoad() {
+        let docEl = document.documentElement;
+        let item = window.arguments[0].item;
+        let vbox = getPreviewForEvent(item);
+        let descr = document.getElementById("conflicts-description");
+        descr.parentNode.insertBefore(vbox, descr);
+
+        // TODO These strings should move to DTD files, but we don't want to
+        // disrupt string freeze right now. For that matter, this dialog
+        // should be reworked!
+        docEl.title =  cal.calGetString("calendar", "itemModifiedOnServerTitle");
+        descr.textContent = cal.calGetString("calendar", "itemModifiedOnServer");
+
+        if (window.arguments[0].mode == "modify") {
+            descr.textContent += cal.calGetString("calendar", "modifyWillLoseData");
+            docEl.getButton("accept").setAttribute("label", cal.calGetString("calendar", "proceedModify"));
+        } else {
+            promptMessage += cal.calGetString("calendar", "deleteWillLoseData");
+            docEl.getButton("accept").setAttribute("label", cal.calGetString("calendar", "proceedDelete"));
+        }
+
+        docEl.getButton("cancel").setAttribute("label", cal.calGetString("calendar", "updateFromServer"));
+
+        window.sizeToContent();
+    }
+
+    function onAccept() {
+        window.arguments[0].overwrite = true;
+    }
+
+    function onCancel() {
+        window.arguments[0].overwrite = false;
+    }
+  ]]></script>
+
+  <vbox id="conflicts-vbox">
+    <description id="conflicts-description"
+                 style="max-width: 40em; margin-top: 1ex"/>
+  </vbox>
+</dialog>
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -36,16 +36,17 @@ calendar.jar:
     content/calendar/calendar-view-bindings.css            (content/calendar-view-bindings.css)
     content/calendar/calendar-view-core.xml                (content/calendar-view-core.xml)
     content/calendar/calendar-views.js                     (content/calendar-views.js)
     content/calendar/import-export.js                      (content/import-export.js)
     content/calendar/today-pane.xul                        (content/today-pane.xul)
     content/calendar/today-pane.js                         (content/today-pane.js)
     content/calendar/calendar-alarm-dialog.js              (content/dialogs/calendar-alarm-dialog.js)
     content/calendar/calendar-alarm-dialog.xul             (content/dialogs/calendar-alarm-dialog.xul)
+    content/calendar/calendar-conflicts-dialog.xul         (content/dialogs/calendar-conflicts-dialog.xul)
     content/calendar/calendar-creation.js                  (content/dialogs/calendar-creation.js)
     content/calendar/calendar-dialog-utils.js              (content/dialogs/calendar-dialog-utils.js)
     content/calendar/calendar-error-prompt.xul             (content/dialogs/calendar-error-prompt.xul)
     content/calendar/calendar-event-dialog.css             (content/dialogs/calendar-event-dialog.css)
     content/calendar/calendar-event-dialog.js              (content/dialogs/calendar-event-dialog.js)
     content/calendar/calendar-event-dialog.xul             (content/dialogs/calendar-event-dialog.xul)
     content/calendar/calendar-event-dialog-attendees.xml   (content/dialogs/calendar-event-dialog-attendees.xml)
     content/calendar/calendar-event-dialog-freebusy.xml    (content/dialogs/calendar-event-dialog-freebusy.xml)
--- a/calendar/base/modules/calProviderUtils.jsm
+++ b/calendar/base/modules/calProviderUtils.jsm
@@ -476,16 +476,30 @@ cal.toRFC3339 = function toRFC3339(aDate
         } else {
             // ZULU Time, according to ISO8601's timezone-offset
             str += "Z";
         }
     }
     return str;
 };
 
+cal.promptOverwrite = function cal_promptOverwrite(aMode, aItem) {
+    let window = cal.getCalendarWindow();
+    let args = { item: aItem,
+                 mode: aMode,
+                 overwrite: false };
+
+    window.openDialog("chrome://calendar/content/calendar-conflicts-dialog.xul",
+                      "calendarConflictsDialog",
+                      "chrome,titlebar,modal",
+                      args);
+
+    return args.overwrite;
+};
+
 /**
  * Observer bag implementation taking care to replay open batch notifications.
  */
 cal.ObserverBag = function calObserverBag(iid) {
     this.init(iid);
 };
 cal.ObserverBag.prototype = {
     __proto__: cal.calListenerBag.prototype,
--- a/calendar/base/public/calICalendar.idl
+++ b/calendar/base/public/calICalendar.idl
@@ -275,16 +275,26 @@ interface calICalendar : nsISupports
 
   /**
    * Scope: Attendee
    * Filter items that correspond to an invitation from another
    * user and the current user has not replied to it yet.
    */
   const unsigned long ITEM_FILTER_REQUEST_NEEDS_ACTION = 1 << 17;
 
+  /**
+   * Flags for items that have been created, modified or deleted while
+   * offline.
+   * ITEM_FILTER_OFFLINE_DELETED is a particular case in that elements *must*
+   * be excluded from searches when not specified in the filter mask.
+   */
+  const unsigned long ITEM_FILTER_OFFLINE_CREATED = 1 << 29;
+  const unsigned long ITEM_FILTER_OFFLINE_MODIFIED = 1 << 30;
+  const unsigned long ITEM_FILTER_OFFLINE_DELETED = 1 << 31;
+
   void addObserver( in calIObserver observer );
   void removeObserver( in calIObserver observer );
 
   /**
    * The following five "Item" functions are all asynchronous, and return
    * their results to a calIOperationListener object.
    *
    */
@@ -337,16 +347,19 @@ interface calICalendar : nsISupports
    * status of NS_ERROR_XXXXX.  If the item is immutable,
    * onOperationComplete is called with a status of NS_ERROR_XXXXX.
    *
    * If the generation of the given aNewItem does not match the generation
    * of the internal item (indicating that someone else modified the
    * item), onOperationComplete is called with a status of NS_ERROR_XXXXX
    * and aDetail is set to the latest-version internal immutable item.
    *
+   * If you would like to disable revision checks, pass null as aOldItem. This
+   * will overwrite the item on the server.
+   *
    * @param aNewItem    new version to replace the old one
    * @param aOldItem    caller's view of the item to be changed, as it is now
    * @param aListener   where to call back the results
    * @return            optional operation handle to track the operation
    *
    * The results of the operation are reported through an
    * onOperationComplete call on the listener, with the following
    * parameters:
--- a/calendar/base/public/calIChangeLog.idl
+++ b/calendar/base/public/calIChangeLog.idl
@@ -36,16 +36,64 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "calICalendar.idl"
 
 interface calIGenericOperationListener;
 interface calIOperation;
 
 /**
+ * Interface for managing offline flags in offline storage
+ * (calStorageCalendar), in particular from calICachedCalendar.
+ */
+[scriptable, uuid(36dc2c93-5851-40d2-9ba9-b1f6e682c75c)]
+interface calIOfflineStorage : calICalendar {
+    /**
+     * Mark the item of which the id is passed as parameter as new.
+     *
+     * @param aItem       the item to add
+     * @param aListener   where to call back the results
+     */
+    void addOfflineItem(in calIItemBase aItem, in calIOperationListener aListener);
+
+    /**
+     * Mark the item of which the id is passed as parameter as modified.
+     *
+     * @param aItem       the item to modify
+     * @param aListener   where to call back the results
+     */
+    void modifyOfflineItem(in calIItemBase aItem, in calIOperationListener aListener);
+
+    /**
+     * Mark the item of which the id is passed as parameter as deleted.
+     *
+     * @param aItem       the item to delete
+     * @param aListener   where to call back the results
+     */
+    void deleteOfflineItem(in calIItemBase aItem, in calIOperationListener aListener);
+
+    /**
+     * Retrieves the offline flag for the given item. The flag is returned using the
+     * detail parameter of the onOperationComplete function in calIOperationLIstener.
+     *
+     * @param aItem       the item to reset
+     * @param aListener   where to call back the results
+     */
+    void getItemOfflineFlag(in calIItemBase aItem, in calIOperationListener aListener);
+
+    /**
+     * Remove any offline flag from the item record.
+     *
+     * @param aItem       the item to reset
+     * @param aListener   where to call back the results
+     */
+    void resetItemOfflineFlag(in calIItemBase aItem, in calIOperationListener aListener);
+};
+
+/**
  * Interface for synchronously working providers on storing items,
  * e.g. storage, memory. All modifying commands return after the
  * modification has been performed.
  *
  * @note
  *   This interface is used in conjunction with changelog-based synchronization
  *   and additionally offers storing meta-data for items for this purpose.
  *   The meta data is stored as long as the corresponding items persist in
@@ -95,16 +143,27 @@ interface calISyncWriteCalendar : calICa
  * changelog data. This could for example mean, that the provider can retrieve
  * changes between now and the last sync.
  *
  * Not implementing this interface is perfectly valid for calendars, that need
  * to do a full sync each time anyway (i.e ics)
  */
 [scriptable, uuid(0bf4c6a2-b4c7-4cae-993a-4408d8bded3e)]
 interface calIChangeLog : nsISupports {
+
+    // To denote no offline flag, use null
+    const long OFFLINE_FLAG_CREATED_RECORD = 1;
+    const long OFFLINE_FLAG_MODIFIED_RECORD = 2;
+    const long OFFLINE_FLAG_DELETED_RECORD = 4;
+
+    /**
+     * Enable the changelog calendar to retrieve offline data right after instanciation.
+     */
+    attribute calISyncWriteCalendar offlineStorage;
+
     /**
      * Resets the changelog. This is used if the cache should be refreshed.
      */
     void resetLog();
 
     /**
      * Instructs the calendar to replay remote changes into the given
      * calendar. The calendar itself is responsible for storing anything needed
@@ -112,11 +171,50 @@ interface calIChangeLog : nsISupports {
      *
      * TODO: We might reconsider to replay on calICalendar,
      *       but this complicates implementing this interface
      *       enormously for providers.
      *
      * @param aDestination      The calendar to sync changes into
      * @param aListener         The listener to notify when the operation completes.
      */
-    calIOperation replayChangesOn(in calISyncWriteCalendar aDestination,
-                                  in calIGenericOperationListener aListener);
+    calIOperation replayChangesOn(in calIGenericOperationListener aListener);
+
+    /**
+     * addItemOrUseCache: same as calICalendar::addItem
+     *
+     * @param useCache          use the cache for handling availability errors
+     *
+     * When 'useCache' is set, failure to perform the operation will result in
+     * a write in the cache with the corresponding offline operation, when the
+     * backend supports it. In that case, the listener will thus always
+     * receive a success code. When unset, the behaviour is similar to the
+     * corresponding addItem() invocation.
+     */
+    calIOperation addItemOrUseCache(in calIItemBase aItem,
+                                    in boolean useCache,
+                                    in calIOperationListener aListener);
+    /**
+     * adoptItemOrUseCache: same as calICalendar::adoptItem
+     *
+     * @param useCache          use the cache for handling availability errors
+     */
+    calIOperation adoptItemOrUseCache(in calIItemBase aItem,
+                                      in boolean useCache,
+                                      in calIOperationListener aListener);
+    /**
+     * modifyItemOrUseCache: same as calICalendar::modifyItem
+     *
+     * @param useCache          use the cache for handling availability errors
+     */
+    calIOperation modifyItemOrUseCache(in calIItemBase aNewItem,
+                                       in calIItemBase aOldItem,
+                                       in boolean useCache,
+                                       in calIOperationListener aListener);
+    /**
+     * deleteItemOrUseCache: same as calICalendar::deleteItem
+     *
+     * @param useCache          use the cache for handling availability errors
+     */
+    calIOperation deleteItemOrUseCache(in calIItemBase aItem,
+                                       in boolean useCache,
+                                       in calIOperationListener aListener);
 };
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -32,16 +32,27 @@
  * 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 ***** */
 
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 
+const calICalendar = Components.interfaces.calICalendar;
+const cICL = Components.interfaces.calIChangeLog;
+
+let gNoOpListener = {
+    onGetResult: function(calendar, status, itemType, detail, count, items) {
+    },
+
+    onOperationComplete: function(calendar, status, opType, id, detail) {
+    }
+};
+
 function calCachedCalendarObserverHelper(home, isCachedObserver) {
     this.home = home;
     this.isCachedObserver = isCachedObserver;
 }
 calCachedCalendarObserverHelper.prototype = {
     isCachedObserver: false,
 
     onStartBatch: function() {
@@ -56,20 +67,19 @@ calCachedCalendarObserverHelper.prototyp
         if (this.isCachedObserver) {
             this.home.mObservers.notify("onLoad", [this.home]);
         } else {
             // start sync action after uncached calendar has been loaded.
             // xxx todo, think about:
             // although onAddItem et al have been called, we need to fire
             // an additional onLoad completing the refresh call (->composite)
             var home = this.home;
-            home.synchronize(
-                function(status) {
-                    home.mObservers.notify("onLoad", [home]);
-                });
+            home.synchronize(function(status) {
+                home.mObservers.notify("onLoad", [home]);
+            });
         }
     },
 
     onAddItem: function(aItem) {
         if (this.isCachedObserver) {
             this.home.mObservers.notify("onAddItem", arguments);
         }
     },
@@ -106,35 +116,42 @@ calCachedCalendarObserverHelper.prototyp
 function calCachedCalendar(uncachedCalendar) {
     this.wrappedJSObject = this;
     this.mSyncQueue = [];
     this.mObservers = new cal.ObserverBag(Components.interfaces.calIObserver);
     uncachedCalendar.superCalendar = this;
     uncachedCalendar.addObserver(new calCachedCalendarObserverHelper(this, false));
     this.mUncachedCalendar = uncachedCalendar;
     this.setupCachedCalendar();
+    if (this.supportsChangeLog) {
+        uncachedCalendar.offlineStorage = this.mCachedCalendar;
+    }
+    this.offlineCachedItems = {};
+    this.offlineCachedItemFlags = {};
 }
 calCachedCalendar.prototype = {
     QueryInterface: function cCC_QueryInterface(aIID) {
         if (aIID.equals(Components.interfaces.calISchedulingSupport)) {
             // check whether uncached calendar supports it:
             if (this.mUncachedCalendar.QueryInterface(aIID)) {
                 return this;
             }
         }
-        return doQueryInterface(this, calCachedCalendar.prototype, aIID,
-                                [Components.interfaces.calICalendar,
-                                 Components.interfaces.nsISupports]);
+        return cal.doQueryInterface(this, calCachedCalendar.prototype, aIID,
+                                    [Components.interfaces.calICalendar,
+                                     Components.interfaces.nsISupports]);
     },
 
     mCachedCalendar: null,
     mCachedObserver: null,
     mUncachedCalendar: null,
     mObservers: null,
     mSuperCalendar: null,
+    offlineCachedItems: null,
+    offlineCachedItemFlags: null,
 
     onCalendarUnregistering: function() {
         if (this.mCachedCalendar) {
             this.mCachedCalendar.removeObserver(this.mCachedObserver);
             // Although this doesn't really follow the spec, we know the
             // storage calendar's deleteCalendar method is synchronous.
             // TODO put changes into a different calendar and delete
             // afterwards.
@@ -190,112 +207,419 @@ calCachedCalendar.prototype = {
                 cachedCalendar.addObserver(this.mCachedObserver);
                 this.mCachedCalendar = cachedCalendar;
             }
         } catch (exc) {
             Components.utils.reportError(exc);
         }
     },
 
+    getOfflineAddedItems: function cCC_getOfflineAddedItems(callbackFunc) {
+        let this_ = this;
+        this_.offlineCachedItems = {};
+        let getListener = {
+            onGetResult: function cCC_oOC_cL_onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
+                for each (let item in aItems) {
+                    this_.offlineCachedItems[item.hashId] = item;
+                    this_.offlineCachedItemFlags[item.hashId] = cICL.OFFLINE_FLAG_CREATED_RECORD;
+                }
+            },
+
+            onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar, aStatus, aOpType, aId, aDetail) {
+                this_.getOfflineModifiedItems(callbackFunc);
+            }
+        };
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_ALL_ITEMS | calICalendar.ITEM_FILTER_OFFLINE_CREATED,
+                                      0, null, null, getListener);
+    },
+
+    getOfflineModifiedItems: function cCC_getOfflineModifiedItems(callbackFunc) {
+        let this_ = this;
+        let getListener = {
+            onGetResult: function cCC_oOC_cL_onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
+                for each (let item in aItems) {
+                    this_.offlineCachedItems[item.hashId] = item;
+                    this_.offlineCachedItemFlags[item.hashId] = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
+                }
+            },
+
+            onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar, aStatus, aOpType, aId, aDetail) {
+                this_.getOfflineDeletedItems(callbackFunc);
+            }
+        };
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_OFFLINE_MODIFIED | calICalendar.ITEM_FILTER_ALL_ITEMS,
+                                      0, null, null, getListener);
+    },
+
+    getOfflineDeletedItems: function cCC_getOfflineDeletedItems(callbackFunc) {
+        let this_ = this;
+        let getListener = {
+            onGetResult: function cCC_oOC_cL_onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
+                for each (let item in aItems) {
+                    this_.offlineCachedItems[item.hashId] = item;
+                    this_.offlineCachedItemFlags[item.hashId] = cICL.OFFLINE_FLAG_DELETED_RECORD;
+                }
+            },
+
+            onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar, aStatus, aOpType, aId, aDetail) {
+                if (callbackFunc) {
+                    callbackFunc();
+                }
+            }
+        };
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_OFFLINE_DELETED | calICalendar.ITEM_FILTER_ALL_ITEMS,
+                                      0, null, null, getListener);
+    },
+
     mPendingSync: null,
     mSyncQueue: null,
     synchronize: function cCC_synchronize(respFunc) {
+        var this_ = this;
+        if (this.getProperty("disabled")) {
+            return emptyQueue(Components.results.NS_OK);
+        }
+
         this.mSyncQueue.push(respFunc);
         if (this.mSyncQueue.length > 1) { // don't use mPendingSync here
-            LOG("[calCachedCalendar] sync in action/pending.");
+            cal.LOG("[calCachedCalendar] sync in action/pending.");
             return this.mPendingSync;
         }
 
-        var this_ = this;
         function emptyQueue(status) {
             var queue = this_.mSyncQueue;
             this_.mSyncQueue = [];
             function execResponseFunc(func) {
                 try {
                     func(status);
                 } catch (exc) {
-                    ASSERT(false, exc);
+                    cal.ASSERT(false, exc);
                 }
             }
             queue.forEach(execResponseFunc);
-            LOG("[calCachedCalendar] sync queue empty.");
+            cal.LOG("[calCachedCalendar] sync queue empty.");
             var op = this_.mPendingSync;
             this_.mPendingSync = null;
             return op;
         }
 
         if (this.offline) {
             return emptyQueue(Components.results.NS_OK);
         }
 
         if (this.supportsChangeLog) {
-            LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
+            cal.LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
             var opListener = {
                 thisCalendar : this,
                 onResult: function(op, result) {
                     if (!op || !op.isPending) {
                         var status = (op ? op.status : Components.results.NS_OK);
-                        ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>")+", uri=" + this.thisCalendar.uri.spec + ", result="  +result + ", op=" + op);
-                        LOG("[calCachedCalendar] replayChangesOn finished.");
+                        if (!Components.isSuccessCode(status)) {
+                            cal.ERROR("[calCachedCalendar] replay action failed: " +
+                                      (op ? op.id : "<unknown>")+", uri=" +
+                                      this.thisCalendar.uri.spec + ", result=" +
+                                      result + ", op=" + op);
+                        }
+                        cal.LOG("[calCachedCalendar] replayChangesOn finished.");
                         emptyQueue(status);
                     }
                 }
             };
-            this.mPendingSync = this.mUncachedCalendar.replayChangesOn(this.mCachedCalendar, opListener);
+            this.mPendingSync = this.mUncachedCalendar.replayChangesOn(opListener);
             return this.mPendingSync;
         }
 
-        LOG("[calCachedCalendar] Doing full sync for calendar " + this.uri.spec);
-        // TODO put changes into a different calendar and delete
-        // afterwards.
-        var completeListener = {
+        cal.LOG("[calCachedCalendar] Doing full sync for calendar " + this.uri.spec);
+        let completeListener = {
+            modifiedTimes: {},
             hasRenewedCalendar: false,
             onGetResult: function cCC_oOC_cL_onGetResult(aCalendar,
                                                          aStatus,
                                                          aItemType,
                                                          aDetail,
                                                          aCount,
                                                          aItems) {
                 if (Components.isSuccessCode(aStatus)) {
                     if (!this.hasRenewedCalendar) {
                         // TODO instead of deleting the calendar and creating a new
                         // one, maybe we want to do a "real" sync between the
                         // existing local calendar and the remote calendar.
                         this_.setupCachedCalendar();
                         this.hasRenewedCalendar = true;
                     }
                     for each (var item in aItems) {
+                        // Adding items recd from the Memory Calendar
+                        // These may be different than what the cache has
+                        this.modifiedTimes[item.id] = item.lastModifiedTime;
                         this_.mCachedCalendar.addItem(item, null);
                     }
                 }
             },
 
             onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar,
                                                                          aStatus,
                                                                          aOpType,
                                                                          aId,
                                                                          aDetail) {
-                ASSERT(Components.isSuccessCode(aStatus), "getItems failed: " + aStatus);
+                if (Components.isSuccessCode(aStatus)) {
+                    for each (let item in this_.offlineCachedItems) {
+                        switch (this_.offlineCachedItemFlags[item.hashId]) {
+                            case cICL.OFFLINE_FLAG_CREATED_RECORD:
+                                // Created items are not present on the server, so its safe to adopt them
+                                this_.adoptOfflineItem(item.clone(), null);
+                                break;
+                            case cICL.OFFLINE_FLAG_MODIFIED_RECORD:
+                                // Two Cases Here:
+                                if (item.id in this.modifiedTimes) {
+                                    // The item is still on the server, we just retrieved it in the listener above.
+                                    if (item.lastModifiedTime.compare(this.modifiedTimes[item.id]) < 0) {
+                                        // The item on the server has been modified, ask to overwrite
+                                        cal.WARN("[calCachedCalendar] Item '" + item.title + "' at the server seems to be modified recently.");
+                                        this_.promptOverwrite("modify", item, null, null);
+                                    } else {
+                                        // Our item is newer, just modify the item
+                                        this_.modifyOfflineItem(item, null, null);
+                                    }
+                                } else {
+                                    // The item has been deleted from the server, ask if it should be added again
+                                    cal.WARN("[calCachedCalendar] Item '" + item.title + "' has been deleted from the server");
+                                    if (cal.promptOverwrite("modify", item, null, null)) {
+                                        this_.adoptOfflineItem(item.clone(), null);
+                                    }
+                                }
+                                break;
+                            case cICL.OFFLINE_FLAG_DELETED_RECORD:
+                                if (item.id in this.modifiedTimes) {
+                                    // The item seems to exist on the server...
+                                    if (item.lastModifiedTime.compare(this.modifiedTimes[item.id]) < 0) {
+                                        // ...and has been modified on the server. Ask to overwrite
+                                        cal.WARN("[calCachedCalendar] Item '" + item.title + "' at the server seems to be modified recently.");
+                                        this_.promptOverwrite("delete", item, null, null);
+                                    } else {
+                                        // ...and has not been modified. Delete it now.
+                                        this_.deleteOfflineItem(item, null);
+                                    }
+                                } else {
+                                    // Item has already been deleted from the server, no need to change anything.
+                                }
+                                break;
+                        }
+                    }
+                    this_.offlineCachedItems = {};
+                    this_.offlineCachedItemFlags = {};
+                }
+                this_.playbackAddedItems(function() {this_.mCachedObserver.onLoad(this_.mCachedCalendar);});
                 emptyQueue(aStatus);
             }
         };
-        this.mPendingSync = this.mUncachedCalendar.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
-                                                            0, null,  null, completeListener);
+
+        this.getOfflineAddedItems(function(){
+            this_.mPendingSync = this_.mUncachedCalendar.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
+                                                                    0, null,  null, completeListener);
+        });
         return this.mPendingSync;
     },
 
     onOfflineStatusChanged: function cCC_onOfflineStatusChanged(aNewState) {
         if (aNewState) {
             // Going offline: (XXX get items before going offline?) => we may ask the user to stay online a bit longer
         } else {
             // Going online (start replaying changes to the remote calendar)
             this.refresh();
         }
     },
 
+    //aOldItem is already in the cache
+    promptOverwrite: function cCC_promptOverwrite(aMethod, aItem, aListener, aOldItem) {
+        let overwrite = cal.promptOverwrite(aMethod, aItem, aListener, aOldItem);
+        if (overwrite) {
+            if (aMethod == "modify") {
+                this.modifyOfflineItem(aItem, aOldItem, aListener);
+            } else {
+                this.deleteOfflineItem(aItem, aListener);
+            }
+        }
+    },
+
+    playbackAddedItems: function cCC_playbackAddedItems(callbackFunc) {
+        let this_ = this;
+        let storage = this.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+
+        let resetListener = gNoOpListener;
+
+        let addListener = {
+            itemCount: 0,
+
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    storage.resetItemOfflineFlag(detail, resetListener);
+                } else {
+                    cal.LOG("[calCachedCalendar.js] Unable to playback the items to the server. Will try back again. Aborting\n");
+                    // TODO does something need to be called back?
+                }
+
+                this.itemCount--;
+                if (this.itemCount == 0) {
+                    this_.playbackModifiedItems(callbackFunc);
+                }
+            }
+        };
+
+        let getListener = {
+            items: [],
+
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                this.items = this.items.concat(items);
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (this_.offline) {
+                    cal.LOG("[calCachedCalendar] back to offline mode, reconciliation aborted");
+                    callbackFunc();
+                } else {
+                    cal.LOG("[calCachedCalendar] Adding "  + this.items.length + " items to " + this_.name);
+                    if (this.items.length > 0) {
+                        addListener.itemCount = this.items.length;
+                        for each (var aItem in this.items) {
+                            if (this_.supportsChangeLog) {
+                                this_.mUncachedCalendar.addItemOrUseCache(aItem, false, addListener);
+                            } else {
+                                // default mechanism for providers not implementing calIChangeLog
+                                this_.mUncachedCalendar.adoptItem(aItem.clone(), addListener);
+                            }
+                        }
+                    } else {
+                        this_.playbackModifiedItems(callbackFunc);
+                    }
+                }
+                delete this.items;
+            }
+        };
+
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_ALL_ITEMS | calICalendar.ITEM_FILTER_OFFLINE_CREATED,
+                                      0, null, null, getListener);
+    },
+    playbackModifiedItems: function cCC_playbackModifiedItems(callbackFunc) {
+        let this_ = this;
+        let storage = this.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+
+        let resetListener = gNoOpListener;
+
+        let modifyListener = {
+            itemCount: 0,
+            onGetResult: function(calendar, status, itemType, detail, count, items) {},
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    storage.resetItemOfflineFlag(detail, resetListener);
+                } else {
+                    cal.ERROR("[calCachedCalendar] Could not modify item " + id + " - " + detail);
+                }
+
+                this.itemCount--;
+                if (this.itemCount == 0) {
+                    // All items have been pushed in and this is the last.
+                    // Continue with deleted items.
+                    this_.playbackDeletedItems(callbackFunc);
+                }
+            }
+        };
+
+        let getListener = {
+            items: [],
+
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                this.items = this.items.concat(items);
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (this_.offline) {
+                    cal.LOG("[calCachedCalendar] Returning to offline mode, reconciliation aborted");
+                    callbackFunc();
+                } else {
+                    cal.LOG("[calCachedCalendar] Modifying " + this.items.length + " items in " + this_.name);
+                    if (this.items.length > 0) {
+                        modifyListener.itemCount = this.items.length;
+                        for each (let item in this.items) {
+                            if (this_.supportsChangeLog) {
+                                // The calendar supports the changelog functions, let it modify the item
+                                // TODO is it ok to not have the old item here? Pass null or the new item?
+                                this_.mUncachedCalendar.modifyItemOrUseCache(item, item, false, modifyListener);
+                            } else {
+                                // Default strategy for providers not implementing calIChangeLog
+                                this_.mUncachedCalendar.modifyItem(item, null, modifyListener);
+                            }
+                        }
+                    } else {
+                        this_.playbackDeletedItems(callbackFunc);
+                    }
+                }
+                delete this.items;
+            }
+        };
+
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_OFFLINE_MODIFIED | calICalendar.ITEM_FILTER_ALL_ITEMS,
+                                      0, null, null, getListener);
+    },
+
+    playbackDeletedItems: function cCC_playbackDeletedItems(callbackFunc) {
+        let this_ = this;
+        let storage = this.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+
+        let resetListener = this.gNoOpListener;
+
+        let deleteListener = {
+            itemCount: 0,
+            onGetResult: function(calendar, status, itemType, detail, count, items) {},
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    this_.mCachedCalendar.deleteItem(detail, resetListener);
+                    this.itemCount--;
+                    if (this.itemCount == 0 && callbackFunc) {
+                        callbackFunc();
+                    }
+                } else {
+                    // TODO do something on error
+                    cal.WARN("[calCachedCalendar] failed to playback deleted item " + id + " - " + detail);
+                }
+            }
+        };
+
+        let getListener = {
+            items: [],
+
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                this.items = this.items.concat(items);
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (this_.offline) {
+                    cal.LOG("[calCachedCalendar] Returning to offline mode, reconciliation aborted");
+                    callbackFunc();
+                } else {
+                    cal.LOG("[calCachedCalendar] Deleting "  + this.items.length + " items from " + this_.name);
+                    if (this.items.length > 0) {
+                        deleteListener.itemCount = this.items.length;
+                        for each (var aItem in this.items) {
+                            if (this_.supportsChangeLog) {
+                                this_.mUncachedCalendar.deleteItemOrUseCache(aItem, false, deleteListener);
+                            } else {
+                                //Default strategy for providers not implementing calIChangeLog
+                                this_.mUncachedCalendar.deleteItem(aItem, deleteListener);
+                            }
+                        }
+                    } else if (callbackFunc) {
+                        callbackFunc();
+                    }
+                }
+                delete this.items;
+            }
+        };
+
+        this.mCachedCalendar.getItems(calICalendar.ITEM_FILTER_OFFLINE_DELETED | calICalendar.ITEM_FILTER_ALL_ITEMS,
+                                      0, null, null, getListener);
+    },
+
     get superCalendar() {
         return this.mSuperCalendar && this.mSuperCalendar.superCalendar || this;
     },
     set superCalendar(val) {
         return (this.mSuperCalendar = val);
     },
 
     get offline() {
@@ -304,16 +628,30 @@ calCachedCalendar.prototype = {
     get supportsChangeLog() {
         return calInstanceOf(this.mUncachedCalendar, Components.interfaces.calIChangeLog);
     },
 
     get canRefresh() { // enable triggering sync using the reload button
         return true;
     },
     refresh: function() {
+        if (this.offline) {
+            this.downstreamRefresh();
+        }
+        else {
+            /* we first ensure that any remaining offline items are reconciled with the calendar server */
+            let this_ = this;
+            if (this.supportsChangeLog) {
+                this.playbackAddedItems(this.downstreamRefresh.bind(this));
+            } else {
+                this.downstreamRefresh();
+            }
+        }
+    },
+    downstreamRefresh: function() {
         if (this.mUncachedCalendar.canRefresh && !this.offline) {
             return this.mUncachedCalendar.refresh(); // will trigger synchronize once the calendar is loaded
         } else {
             var this_ = this;
             return this.synchronize(
                 function(status) { // fire completing onLoad for this refresh call
                     this_.mCachedObserver.onLoad(this_.mCachedCalendar);
                 });
@@ -327,117 +665,156 @@ calCachedCalendar.prototype = {
         this.mObservers.remove(aObserver);
     },
 
     addItem: function(item, listener) {
         return this.adoptItem(item.clone(), listener);
     },
     adoptItem: function(item, listener) {
         if (this.offline) {
-            ASSERT(false, "unexpected!");
-            if (listener) {
-                listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
-                                             Components.interfaces.calIOperation.ADD, null, null);
-            }
-            return null;
+            this.adoptOfflineItem(item, listener);
+            return;
         }
         // Forwarding add/modify/delete to the cached calendar using the calIObserver
         // callbacks would be advantageous, because the uncached provider could implement
         // a true push mechanism firing without being triggered from within the program.
         // But this would mean the uncached provider fires on the passed
         // calIOperationListener, e.g. *before* it fires on calIObservers
         // (because that order is undefined). Firing onOperationComplete before onAddItem et al
         // would result in this facade firing onOperationComplete even though the modification
         // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
         // Result is that we currently stick to firing onOperationComplete if the cached calendar
         // has performed the modification, see below:
         var this_ = this;
         var opListener = {
             onGetResult: function(calendar, status, itemType, detail, count, items) {
-                ASSERT(false, "unexpected!");
+                cal.ASSERT(false, "unexpected!");
             },
             onOperationComplete: function(calendar, status, opType, id, detail) {
-                if (Components.isSuccessCode(status)) {
+                if (Components.isSuccessCode(status) && !this_.supportsChangeLog) {
                     this_.mCachedCalendar.addItem(detail, listener);
                 } else if (listener) {
                     listener.onOperationComplete(this_, status, opType, id, detail);
                 }
             }
         }
+        if (this.supportsChangeLog) {
+            return this.mUncachedCalendar.addItemOrUseCache(item, true, opListener);
+        }
         return this.mUncachedCalendar.adoptItem(item, opListener);
     },
+    adoptOfflineItem: function(item, listener) {
+        var this_ = this;
+        var opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    var storage = this_.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+                    storage.addOfflineItem(detail, listener);
+                } else if (listener) {
+                    listener.onOperationComplete(this_, status, opType, id, detail);
+                }
+            }
+        };
+        this.mCachedCalendar.adoptItem(item, opListener);
+    },
 
     modifyItem: function(newItem, oldItem, listener) {
         if (this.offline) {
-            ASSERT(false, "unexpected!");
-            if (listener) {
-                listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
-                                             Components.interfaces.calIOperation.MODIFY, null, null);
+            this.modifyOfflineItem(newItem, oldItem, listener);
+            return;
+        }
+
+        // Forwarding add/modify/delete to the cached calendar using the calIObserver
+        // callbacks would be advantageous, because the uncached provider could implement
+        // a true push mechanism firing without being triggered from within the program.
+        // But this would mean the uncached provider fires on the passed
+        // calIOperationListener, e.g. *before* it fires on calIObservers
+        // (because that order is undefined). Firing onOperationComplete before onAddItem et al
+        // would result in this facade firing onOperationComplete even though the modification
+        // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
+        // Result is that we currently stick to firing onOperationComplete if the cached calendar
+        // has performed the modification, see below:
+        var this_ = this;
+        var opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    this_.mCachedCalendar.modifyItem(detail, oldItem, listener);
+                } else if (listener) {
+                    listener.onOperationComplete(this_, status, opType, id, detail);
+                }
             }
-            return null;
+        }
+        if (this.supportsChangeLog) {
+            return this.mUncachedCalendar.modifyItemOrUseCache(newItem, oldItem, true, opListener);
+        } else {
+            return this.mUncachedCalendar.modifyItem(newItem, oldItem, opListener);
+        }
+    },
+    modifyOfflineItem: function(newItem, oldItem, listener) {
+        var this_ = this;
+        var opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    var storage = this_.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+                    storage.modifyOfflineItem(detail, listener);
+                } else if (listener) {
+                    listener.onOperationComplete(this_, status, opType, id, detail);
+                }
+            }
+        };
+
+        this.mCachedCalendar.modifyItem(newItem, oldItem, opListener);
+    },
+
+    deleteItem: function(item, listener) {
+        if (this.offline) {
+            this.deleteOfflineItem(item, listener);
+            return;
         }
         // Forwarding add/modify/delete to the cached calendar using the calIObserver
         // callbacks would be advantageous, because the uncached provider could implement
         // a true push mechanism firing without being triggered from within the program.
         // But this would mean the uncached provider fires on the passed
         // calIOperationListener, e.g. *before* it fires on calIObservers
         // (because that order is undefined). Firing onOperationComplete before onAddItem et al
         // would result in this facade firing onOperationComplete even though the modification
         // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
         // Result is that we currently stick to firing onOperationComplete if the cached calendar
         // has performed the modification, see below:
         var this_ = this;
         var opListener = {
             onGetResult: function(calendar, status, itemType, detail, count, items) {
-                ASSERT(false, "unexpected!");
-            },
-            onOperationComplete: function(calendar, status, opType, id, detail) {
-                if (Components.isSuccessCode(status)) {
-                    this_.mCachedCalendar.modifyItem(detail, oldItem, listener);
-                } else if (listener) {
-                    listener.onOperationComplete(this_, status, opType, id, detail);
-                }
-            }
-        }
-        return this.mUncachedCalendar.modifyItem(newItem, oldItem, opListener);
-    },
-
-    deleteItem: function(item, listener) {
-        if (this.offline) {
-            ASSERT(false, "unexpected!");
-            if (listener) {
-                listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
-                                             Components.interfaces.calIOperation.DELETE, null, null);
-            }
-            return null;
-        }
-        // Forwarding add/modify/delete to the cached calendar using the calIObserver
-        // callbacks would be advantageous, because the uncached provider could implement
-        // a true push mechanism firing without being triggered from within the program.
-        // But this would mean the uncached provider fires on the passed
-        // calIOperationListener, e.g. *before* it fires on calIObservers
-        // (because that order is undefined). Firing onOperationComplete before onAddItem et al
-        // would result in this facade firing onOperationComplete even though the modification
-        // hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
-        // Result is that we currently stick to firing onOperationComplete if the cached calendar
-        // has performed the modification, see below:
-        var this_ = this;
-        var opListener = {
-            onGetResult: function(calendar, status, itemType, detail, count, items) {
-                ASSERT(false, "unexpected!");
+                cal.ASSERT(false, "unexpected!");
             },
             onOperationComplete: function(calendar, status, opType, id, detail) {
                 if (Components.isSuccessCode(status)) {
                     this_.mCachedCalendar.deleteItem(item, listener);
                 } else if (listener) {
                     listener.onOperationComplete(this_, status, opType, id, detail);
                 }
             }
         }
+        if (this.supportsChangeLog) {
+            return this.mUncachedCalendar.deleteItemOrUseCache(item, true, opListener);
+        }
         return this.mUncachedCalendar.deleteItem(item, opListener);
+    },
+    deleteOfflineItem: function(item, listener) {
+        /* We do not delete the item from the cache, as we will need it when reconciling the cache content and the server content. */
+        var storage = this.mCachedCalendar.QueryInterface(Components.interfaces.calIOfflineStorage);
+        storage.deleteOfflineItem(item, listener);
     }
 };
 (function() {
     function defineForwards(proto, targetName, functions, getters, gettersAndSetters) {
         function defineForwardGetter(attr) {
             proto.__defineGetter__(attr, function() { return this[targetName][attr]; });
         }
         function defineForwardGetterAndSetter(attr) {
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -290,16 +290,17 @@ function getCalendarDirectory() {
  *
  * @param aCalendar     The calendar to check
  * @return              True if the calendar is writable
  */
 function isCalendarWritable(aCalendar) {
     return (!aCalendar.getProperty("disabled") &&
             !aCalendar.readOnly &&
             (!getIOService().offline ||
+             aCalendar.getProperty("cache.enabled") ||
              aCalendar.getProperty("requiresNetwork") === false));
 }
 
 /**
  * Opens the Create Calendar wizard
  *
  * @param aCallback  a function to be performed after calendar creation
  */
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -54,38 +54,38 @@ Components.utils.import("resource://cale
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAuthUtils.jsm");
 
 //
 // calDavCalendar.js
 //
 
 const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
+const cICL = Components.interfaces.calIChangeLog;
 
 function calDavCalendar() {
     this.initProviderBase();
     this.unmappedProperties = [];
     this.mUriParams = null;
     this.mItemInfoCache = {};
     this.mDisabled = false;
     this.mCalHomeSet = null;
     this.mInboxUrl = null;
     this.mOutboxUrl = null;
     this.mCalendarUserAddress = null;
     this.mPrincipalUrl = null;
     this.mSenderAddress = null;
-    this.mPathIndex = {};
+    this.mHrefIndex = {};
     this.mAuthScheme = null;
     this.mAuthRealm = null;
     this.mObserver = null;
     this.mFirstRefreshDone = false;
-    this.mTargetCalendar = null;
+    this.mOfflineStorage = null;
     this.mQueuedQueries = [];
     this.mCtag = null;
-    this.mOldCtag = null;
 
     // By default, support both events and todos.
     this.mGenerallySupportedItemTypes = ["VEVENT", "VTODO"];
     this.mSupportedItemTypes = this.mGenerallySupportedItemTypes.slice(0);
 }
 
 // some shorthand
 const calICalendar = Components.interfaces.calICalendar;
@@ -94,19 +94,18 @@ const calIFreeBusyInterval = Components.
 const calICalDavCalendar = Components.interfaces.calICalDavCalendar;
 
 // used in checking calendar URI for (Cal)DAV-ness
 const kDavResourceTypeNone = 0;
 const kDavResourceTypeCollection = 1;
 const kDavResourceTypeCalendar = 2;
 
 // used for etag checking
-const CALDAV_ADOPT_ITEM = 1;
-const CALDAV_MODIFY_ITEM = 2;
-const CALDAV_DELETE_ITEM = 3;
+const CALDAV_MODIFY_ITEM = "modify";
+const CALDAV_DELETE_ITEM = "delete";
 
 calDavCalendar.prototype = {
     __proto__: cal.ProviderBase.prototype,
 
     classID: Components.ID("{a35fc6ea-3d92-11d9-89f9-00045ace3b8d}"),
     contractID: "@mozilla.org/calendar/calendar;1?type=caldav",
     classDescription: "Calendar CalDAV back-end",
 
@@ -146,28 +145,28 @@ calDavCalendar.prototype = {
         return this.mSupportedItemTypes;
     },
 
     get isCached() {
         return (this != this.superCalendar);
     },
 
     ensureTargetCalendar: function caldav_ensureTargetCalendar() {
-        if (!this.isCached && !this.mTargetCalendar) {
+        if (!this.isCached && !this.mOfflineStorage) {
             // If this is a cached calendar, the actual cache is taken care of
             // by the calCachedCalendar facade. In any other case, we use a
             // memory calendar to cache things.
-            this.mTargetCalendar = Components
+            this.mOfflineStorage = Components
                                    .classes["@mozilla.org/calendar/calendar;1?type=memory"]
                                    .createInstance(Components.interfaces.calISyncWriteCalendar);
 
-            this.mTargetCalendar.superCalendar = this;
+            this.mOfflineStorage.superCalendar = this;
             this.mObserver = new calDavObserver(this);
-            this.mTargetCalendar.addObserver(this.mObserver);
-            this.mTargetCalendar.setProperty("relaxedMode", true);
+            this.mOfflineStorage.addObserver(this.mObserver);
+            this.mOfflineStorage.setProperty("relaxedMode", true);
         }
     },
 
     //
     // calICalendarProvider interface
     //
     get prefChromeOverlay() {
         return null;
@@ -180,103 +179,130 @@ calDavCalendar.prototype = {
     createCalendar: function caldav_createCalendar() {
         throw NS_ERROR_NOT_IMPLEMENTED;
     },
 
     deleteCalendar: function caldav_deleteCalendar(cal, listener) {
         throw NS_ERROR_NOT_IMPLEMENTED;
     },
 
+    // calIChangeLog interface
+    get offlineStorage() {
+        return this.mOfflineStorage;
+    },
 
-    // calIChangeLog interface
+    set offlineStorage(storage) {
+        this.mOfflineStorage = storage;
+        this.fetchCachedMetaData();
+    },
+
     resetLog: function caldav_resetLog() {
-        if (this.isCached && this.mTargetCalendar) {
-            this.mTargetCalendar.startBatch();
+        if (this.isCached && this.mOfflineStorage) {
+            this.mOfflineStorage.startBatch();
             try {
-                try {
-                    this.mCtag = null;
-                    this.mOldCtag = null;
-                    this.mTargetCalendar.deleteMetaData("ctag");
-                } catch(e) {
-                    cal.ERROR(e);
-                }
-                try {
-                    this.mWebdavSyncToken = null;
-                    this.mTargetCalendar.deleteMetaData("webdav-sync-token");
-                } catch(e) {
-                    cal.ERROR(e);
-                }
-
-                for (var itemId in this.mItemInfoCache) {
-                    this.mTargetCalendar.deleteMetaData(itemId);
+                for (let itemId in this.mItemInfoCache) {
+                    this.mOfflineStorage.deleteMetaData(itemId);
                     delete this.mItemInfoCache[itemId];
                 }
             } finally {
-                this.mTargetCalendar.endBatch();
+                this.mOfflineStorage.endBatch();
             }
         }
     },
 
-    // in calISyncWriteCalendar aDestination,
+    get offlineCachedProperties() {
+        return [ "mAuthScheme", "mAuthRealm", "mHasWebdavSyncSupport",
+                "mCtag", "mWebdavSyncToken", "mSupportedItemTypes",
+                "mPrincipalUrl", "mCalHomeSet",
+                "mShouldPollInbox", "hasAutoScheduling", "mHaveScheduling",
+                "mCalendarUserAddress", "mShouldPollInbox", "mOutboxUrl" ];
+    },
+
+    saveCalendarProperties: function caldav_saveCalendarProperties() {
+        let properties = {};
+        for each (let property in this.offlineCachedProperties) {
+            if (this[property] !== undefined) {
+                properties[property] = this[property];
+            }
+        }
+        this.mOfflineStorage.setMetaData("calendar-properties", JSON.stringify(properties));
+    },
+    restoreCalendarProperties: function caldav_restoreCalendarProperties(data) {
+        let properties = JSON.parse(data);
+        for each (let property in this.offlineCachedProperties) {
+            if (properties[property] !== undefined) {
+                this[property] = properties[property];
+            }
+        }
+    },
+
     // in calIGenericOperationListener aListener
-    replayChangesOn: function caldav_replayChangesOn(aDestination, aChangeLogListener) {
-        if (!this.mTargetCalendar) {
-            this.mTargetCalendar = aDestination.wrappedJSObject;
-            this.fetchItemVersions();
+    replayChangesOn: function caldav_replayChangesOn(aChangeLogListener) {
+        if (!this.mCheckedServerInfo) {
+            // If we haven't refreshed yet, then we should check the resource
+            // type first. This will call refresh() again afterwards.
             this.checkDavResourceType(aChangeLogListener);
         } else {
             this.safeRefresh(aChangeLogListener);
         }
     },
-
-    fetchItemVersions: function caldav_fetchItemVersions() {
+    setMetaData: function caldav_setMetaData(id, path, etag, isInboxItem) {
+        if (this.mOfflineStorage.setMetaData) {
+            if (id) {
+                var dataString = [etag,path,(isInboxItem ? "true" : "false")].join("\u001A");
+                this.mOfflineStorage.setMetaData(id, dataString);
+            } else {
+                cal.LOG("CalDAV: cannot store meta data without an id");
+            }
+        } else {
+            cal.ERROR("CalDAV: calendar storage does not support meta data");
+        }
+    },
+    fetchCachedMetaData: function caldav_fetchCachedMetaData() {
         var cacheIds = {};
         var cacheValues = {};
-        this.mTargetCalendar.getAllMetaData({}, cacheIds, cacheValues);
+        this.mOfflineStorage.getAllMetaData({}, cacheIds, cacheValues);
         cacheIds = cacheIds.value;
         cacheValues = cacheValues.value;
+        let needsResave = false;
+        let calendarProperties = null;
         for (var count = 0; count < cacheIds.length; count++) {
             var itemId = cacheIds[count];
             var itemData = cacheValues[count];
             if (itemId == "ctag") {
                 this.mCtag = itemData;
+                this.mOfflineStorage.deleteMetaData("ctag");
             } else if (itemId == "webdav-sync-token") {
                 this.mWebdavSyncToken = itemData;
+                this.mOfflineStorage.deleteMetaData("sync-token");
+            } else if (itemId == "calendar-properties") {
+                this.restoreCalendarProperties(itemData);
+                this.mCheckedServerInfo = true;
+                this.setProperty("currentStatus", Components.results.NS_OK);
+                this.readOnly = false;
+                this.disabled = false;
             } else {
                 var itemDataArray = itemData.split("\u001A");
                 var etag = itemDataArray[0];
                 var resourcePath = itemDataArray[1];
                 var isInboxItem = itemDataArray[2];
                 if (itemDataArray.length == 3) {
-                    this.mPathIndex[resourcePath] = itemId;
+                    this.mHrefIndex[resourcePath] = itemId;
                     var locationPath = resourcePath
                         .substr(this.mLocationPath.length);
                     var item = { etag: etag,
                                  isNew: false,
                                  locationPath: locationPath,
                                  isInboxItem: (isInboxItem == "true")};
                     this.mItemInfoCache[itemId] = item;
                 }
             }
         }
     },
 
-    setMetaData: function caldav_setMetaData(id, path, etag, isInboxItem) {
-        if (this.mTargetCalendar.setMetaData) {
-            if (id) {
-                var dataString = [etag,path,(isInboxItem ? "true" : "false")].join("\u001A");
-                this.mTargetCalendar.setMetaData(id, dataString);
-            } else {
-                cal.LOG("CAlDAV: cannot store meta data without an id");
-            }
-        } else {
-            cal.LOG("CalDAV: calendar storage does not support meta data");
-        }
-    },
-
     //
     // calICalendar interface
     //
 
     // readonly attribute AUTF8String type;
     get type() { return "caldav"; },
 
     mDisabled: true,
@@ -361,18 +387,17 @@ calDavCalendar.prototype = {
 
     mFirstRefreshDone: false,
 
     mQueuedQueries: null,
 
     mCtag: null,
     mOldCtag: null,
 
-    mTargetCalendar: null,
-
+    mOfflineStorage: null,
     // Contains the last valid synctoken returned
     // from the server with Webdav Sync enabled servers
     mWebdavSyncToken: null,
     // Indicates that the server supports Webdav Sync
     // see: http://tools.ietf.org/html/draft-daboo-webdav-sync
     mHasWebdavSyncSupport: false,
 
     get authRealm() {
@@ -440,89 +465,71 @@ calDavCalendar.prototype = {
                 return (this.supportedItemTypes.indexOf("VEVENT") > -1);
             case "capabilities.autoschedule.supported":
                 return this.hasAutoScheduling;
         }
         return this.__proto__.__proto__.getProperty.apply(this, arguments);
     },
 
     promptOverwrite: function caldav_promptOverwrite(aMethod, aItem, aListener, aOldItem) {
-        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                      .getService(Components.interfaces.nsIPromptService);
-
-        var promptTitle = calGetString("calendar", "itemModifiedOnServerTitle");
-        var promptMessage = calGetString("calendar", "itemModifiedOnServer");
-        var buttonLabel1;
-
-        if (aMethod == CALDAV_MODIFY_ITEM) {
-            promptMessage += calGetString("calendar", "modifyWillLoseData");
-            buttonLabel1 = calGetString("calendar", "proceedModify");
-        } else {
-            promptMessage += calGetString("calendar", "deleteWillLoseData");
-            buttonLabel1 = calGetString("calendar", "proceedDelete");
-        }
-
-        var buttonLabel2 = calGetString("calendar", "updateFromServer");
-
-        var flags = promptService.BUTTON_TITLE_IS_STRING *
-                    promptService.BUTTON_POS_0 +
-                    promptService.BUTTON_TITLE_IS_STRING *
-                    promptService.BUTTON_POS_1;
-
-        var choice = promptService.confirmEx(null, promptTitle, promptMessage,
-                                             flags, buttonLabel1, buttonLabel2,
-                                             null, null, {});
-
-        if (choice == 0) {
+        let overwrite = cal.promptOverwrite(aMethod, aItem, aListener, aOldItem);
+        if (overwrite) {
             if (aMethod == CALDAV_MODIFY_ITEM) {
-                this.doModifyItem(aItem, aOldItem, aListener, true);
+                this.doModifyItemOrUseCache(aItem, aOldItem, true, aListener, true);
             } else {
-                this.doDeleteItem(aItem, aListener, true, false, null);
+                this.doDeleteItemOrUseCache(aItem, true, aListener, true, false, null);
             }
         } else {
             this.getUpdatedItem(aItem, aListener);
         }
-
     },
 
     mItemInfoCache: null,
 
-    mPathIndex: null,
+    mHrefIndex: null,
 
     /**
      * addItem()
-     * we actually use doAdoptItem()
+     * we actually use doAdoptItemOrUseCache()
      *
      * @param aItem       item to add
      * @param aListener   listener for method completion
      */
     addItem: function caldav_addItem(aItem, aListener) {
-        var newItem = aItem.clone();
-        return this.doAdoptItem(newItem, aListener, false);
+        return this.addItemOrUseCache(aItem, true, aListener);
     },
 
+    addItemOrUseCache: function caldav_addItemOrUseCache(aItem, useCache, aListener){
+        let newItem = aItem.clone();
+        this.adoptItemOrUseCache(newItem, useCache, aListener);
+    },
     /**
      * adoptItem()
-     * we actually use doAdoptItem()
+     * we actually use doAdoptItemOrUseCache()
      *
      * @param aItem       item to check
      * @param aListener   listener for method completion
      */
     adoptItem: function caldav_adoptItem(aItem, aListener) {
-        return this.doAdoptItem(aItem, aListener, false);
+        return this.adoptItemOrUseCache(aItem, true, aListener);
+    },
+
+    adoptItemOrUseCache: function caldav_adoptItemOrUseCache(aItem, useCache, aListener){
+        return this.doAdoptItemOrUseCache(aItem, useCache, aListener, false);
     },
 
     /**
      * Performs the actual addition of the item to CalDAV store
      *
      * @param aItem       item to add
      * @param aListener   listener for method completion
-     * @param aIgnoreEtag ignore item etag
+     * @param useCache    flag to use the cache when a server failure occurs
+     * @param aIgnoreEtag flag to indicate ignoring of Etag
      */
-    doAdoptItem: function caldav_doAdoptItem(aItem, aListener, aIgnoreEtag) {
+    doAdoptItemOrUseCache: function caldav_doAdoptItemOrUseCache(aItem, useCache, aListener, aIgnoreEtag){
         if (aItem.id == null && aItem.isMutable) {
             aItem.id = cal.getUUID();
         }
 
         if (aItem.id == null) {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.ADD,
@@ -568,16 +575,20 @@ calDavCalendar.prototype = {
             if (status == 201 || status == 204) {
                 cal.LOG("CalDAV: Item added to " + thisCalendar.name + " successfully");
 
                 // Some CalDAV servers will modify items on PUT (add X-props,
                 // for instance) so we'd best re-fetch in order to know
                 // the current state of the item
                 // Observers will be notified in getUpdatedItem()
                 thisCalendar.getUpdatedItem(parentItem, aListener);
+            } else if (status >= 500 && status <= 510 &&
+                       useCache && thisCalendar.mOfflineStorage) {
+                cal.LOG("[calDavCalendar] Server unavailability code received. Items are being put into cache for a later try");
+                thisCalendar.adoptOfflineItem(aItem, aListener);
             } else {
                 if (status > 999) {
                     status = "0x" + status.toString(16);
                 }
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
                 } catch(e) {}
@@ -595,44 +606,81 @@ calDavCalendar.prototype = {
 
         parentItem.calendar = this.superCalendar;
 
         let httpchannel = cal.prepHttpChannel(itemUri,
                                               serializedItem,
                                               "text/calendar; charset=utf-8",
                                               this);
 
-
         if (!aIgnoreEtag) {
             httpchannel.setRequestHeader("If-None-Match", "*", false);
         }
 
         cal.sendHttpRequest(cal.createStreamLoader(), httpchannel, addListener);
     },
 
+    adoptOfflineItem: function(item, listener) {
+        let thisCalendar = this;
+        let opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    let storage = thisCalendar.mOfflineStorage.QueryInterface(Components.interfaces.calIOfflineStorage);
+                    storage.addOfflineItem(detail, listener);
+                } else if (listener) {
+                    listener.onOperationComplete(thisCalendar, status, opType, id, detail);
+                }
+            }
+        };
+        thisCalendar.mOfflineStorage.adoptItem(item, opListener);
+    },
+
     /**
      * modifyItem(); required by calICalendar.idl
-     * we actually use doModifyItem()
+     * we actually use modifyItemOrUseCache()
      *
      * @param aItem       item to check
      * @param aListener   listener for method completion
-     */
+    */
     modifyItem: function caldav_modifyItem(aNewItem, aOldItem, aListener) {
-        return this.doModifyItem(aNewItem, aOldItem, aListener, false);
+        return this.modifyItemOrUseCache(aNewItem, aOldItem, true, aListener);
+    },
+
+    modifyItemOrUseCache: function caldav_modifyItemOrUseCache(aNewItem, aOldItem, useCache, aListener){
+        let thisCalendar = this;
+        let opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                let offline_flag = detail;
+                if ((offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
+                     offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) && useCache) {
+                    thisCalendar.modifyOfflineItem(aNewItem, aOldItem, aListener);
+                } else {
+                    thisCalendar.doModifyItemOrUseCache(aNewItem, aOldItem, useCache, aListener, false);
+                }
+            }
+        };
+        let storage = thisCalendar.mOfflineStorage.QueryInterface(Components.interfaces.calIOfflineStorage);
+        storage.getItemOfflineFlag(aOldItem, opListener);
     },
 
     /**
      * Modifies existing item in CalDAV store.
      *
      * @param aItem       item to check
      * @param aOldItem    previous version of item to be modified
+     * @param useCache    Flag to use the cached entry in case of failure
      * @param aListener   listener from original request
      * @param aIgnoreEtag ignore item etag
      */
-    doModifyItem: function caldav_doModifyItem(aNewItem, aOldItem, aListener, aIgnoreEtag) {
+    doModifyItemOrUseCache: function caldav_doModifyItemOrUseCache(aNewItem, aOldItem, useCache, aListener, aIgnoreEtag){
         if (aNewItem.id == null) {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.MODIFY,
                                          aItem.id,
                                          "ID for modifyItem doesn't exist or is null");
             return;
         }
@@ -675,32 +723,34 @@ calDavCalendar.prototype = {
                 // Some CalDAV servers will modify items on PUT (add X-props,
                 // for instance) so we'd best re-fetch in order to know
                 // the current state of the item
                 // Observers will be notified in getUpdatedItem()
                 thisCalendar.getUpdatedItem(aNewItem, aListener);
                 // SOGo has calendarUri == inboxUri so we need to be careful
                 // about deletions
                 if (wasInboxItem && thisCalendar.mShouldPollInbox) {
-                    thisCalendar.doDeleteItem(aNewItem, null, true, true, null);
+                    thisCalendar.doDeleteItemOrUseCache(aNewItem, true, null, true, true, null);
                 }
             } else if (status == 412) {
                 thisCalendar.promptOverwrite(CALDAV_MODIFY_ITEM, aNewItem,
                                              aListener, aOldItem);
+            } else if ((status >= 500 && status <= 510) && (useCache && thisCalendar.mOfflineStorage)) {
+                cal.LOG("[calDavCalendar] doModifyItemOrUseCache received status code of server unavailibity. Putting entry in cache for later try.");
+                thisCalendar.modifyOfflineItem(aNewItem, aOldItem, aListener);
             } else {
                 if (status > 999) {
                     status = "0x " + status.toString(16);
                 }
 
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
                 } catch(e) {}
 
-
                 cal.ERROR("CalDAV: Unexpected status modifying item to " +
                         thisCalendar.name + ": " + status + "\n" +
                         responseBody + "\n" +
                         modifiedItemICS);
 
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_PUT_ERROR,
                                             status,
                                             responseBody);
@@ -716,37 +766,91 @@ calDavCalendar.prototype = {
             httpchannel.setRequestHeader("If-Match",
                                          this.mItemInfoCache[aNewItem.id].etag,
                                          false);
         }
 
         cal.sendHttpRequest(cal.createStreamLoader(), httpchannel, modListener);
     },
 
+    modifyOfflineItem: function(aNewItem, aOldItem, aListener) {
+        let thisCalendar = this;
+        let opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                if (Components.isSuccessCode(status)) {
+                    // Modify the offline item in the storage, passing the
+                    // listener will make sure its notified
+                    let storage = thisCalendar.mOfflineStorage.QueryInterface(Components.interfaces.calIOfflineStorage);
+                    storage.modifyOfflineItem(detail, aListener);
+                } else if (aListener) {
+                    // If there was not a success, then we need to notify the
+                    // listener ourselves
+                    aListener.onOperationComplete(thisCalendar.superCalendar, status, opType, id, detail);
+                }
+            }
+        };
+        thisCalendar.mOfflineStorage.modifyItem(aNewItem, aOldItem, opListener);
+    },
+
     /**
      * deleteItem(); required by calICalendar.idl
-     * the actual deletion is done in doDeleteItem()
+     * the actual deletion is done in deleteItemOrUseCache()
      *
      * @param aItem       item to delete
      * @param aListener   listener for method completion
      */
     deleteItem: function caldav_deleteItem(aItem, aListener) {
-        return this.doDeleteItem(aItem, aListener, false);
+        return this.deleteItemOrUseCache(aItem, true, aListener);
     },
 
+    deleteItemOrUseCache: function caldav_deleteItemOrUseCache(aItem, useCache, aListener){
+        let thisCalendar = this;
+
+        let deleteListener = { //We need a listener because the original doDeleteItemOrUseCache would return null upon successful item deletion
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+                cal.ASSERT(false, "unexpected!");
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                aListener.onOperationComplete(calendar, status, opType, aItem.id, aItem);
+            }
+        };
+
+        // If the item has an offline_flag associated with itself then it is better to
+        // do offline deletion since the items will not be present in the
+        // mItemInfoCache. The items will be reconciled whenever the server becomes available
+        let opListener = {
+            onGetResult: function(calendar, status, itemType, detail, count, items) {
+            },
+            onOperationComplete: function(calendar, status, opType, id, detail) {
+                let offline_flag = detail;
+                if ((offline_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
+                     offline_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) && useCache) {
+                    thisCalendar.deleteOfflineItem(aItem, deleteListener);
+                } else {
+                    thisCalendar.doDeleteItemOrUseCache(aItem, useCache, deleteListener, false);
+                }
+            }
+        };
+        let storage = thisCalendar.mOfflineStorage.QueryInterface(Components.interfaces.calIOfflineStorage);
+        storage.getItemOfflineFlag(aItem, opListener);
+    },
     /**
      * Deletes item from CalDAV store.
      *
      * @param aItem       item to delete
      * @param aListener   listener for method completion
+     * @param useCache    flag to use the cache in case of failure
      * @param aIgnoreEtag ignore item etag
      * @param aFromInbox  delete from inbox rather than calendar
-     * @param aUri        uri of item to delete     */
-    doDeleteItem: function caldav_doDeleteItem(aItem, aListener, aIgnoreEtag, aFromInbox, aUri) {
-
+     * @param aUri        uri of item to delete
+     * */
+    doDeleteItemOrUseCache: function caldav_doDeleteItemOrUseCache(aItem, useCache, aListener, aIgnoreEtag, aFromInbox, aUri){
         if (aItem.id == null) {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.DELETE,
                                          aItem.id,
                                          "ID doesn't exist for deleteItem");
             return;
         }
@@ -778,35 +882,47 @@ calDavCalendar.prototype = {
             //
             if (status == 204 || status == 200) {
                 if (!aFromInbox) {
                     if (thisCalendar.isCached) {
                         // the item is deleted in the storage calendar from calCachedCalendar
                         realListener.onOperationComplete(thisCalendar, status,
                                                          Components.interfaces.calIOperationListener.DELETE,
                                                          null, null);
+                        thisCalendar.mOfflineStorage.deleteMetaData(aItem.id);
                     } else {
-                        thisCalendar.mTargetCalendar.deleteItem(aItem, aListener);
+                        thisCalendar.mOfflineStorage.deleteItem(aItem, aListener);
                     }
                     let decodedPath = thisCalendar.ensureDecodedPath(eventUri.path);
-                    delete thisCalendar.mPathIndex[decodedPath];
+                    delete thisCalendar.mHrefIndex[decodedPath];
                     delete thisCalendar.mItemInfoCache[aItem.id];
                     cal.LOG("CalDAV: Item deleted successfully from calendar" +
                             thisCalendar.name);
                 }
             } else if (status == 412) {
                 // item has either been modified or deleted by someone else
                 // check to see which
 
                 let httpchannel2 = cal.prepHttpChannel(eventUri,
                                                        null,
                                                        null,
                                                        thisCalendar);
                 httpchannel2.requestMethod = "HEAD";
                 cal.sendHttpRequest(cal.createStreamLoader(), httpchannel2, delListener2);
+            } else if ((status >= 500 && status <= 510) && (useCache && thisCalendar.mOfflineStorage)) {
+                cal.LOG("[calDavCalendar] Remote Calendar " + thisCalendar.name + " is currently unavailabile. Putting entry in cache for a later try.");
+                let opListener = {
+                    // We should not return a success code since the listeners can delete the physical item in case of success
+                    onGetResult: function(calendar, status, itemType, detail, count, items) {},
+                    onOperationComplete: function(calendar, status, opType, id, detail) {
+                         aListener.onOperationComplete(calendar, Components.results.NS_ERROR_CONNECTION_REFUSED,
+                                          Components.interfaces.calIOperationListener.GET, aItem.id, aItem);
+                    }
+                };
+                thisCalendar.deleteOfflineItem(aItem, opListener);
             } else {
                 let responseBody="";
                 try {
                     responseBody = cal.convertByteArray(aResult, aResultLength);
                 } catch(e) {}
 
                 cal.ERROR("CalDAV: Unexpected status deleting item from " +
                           thisCalendar.name + ": " + status + "\n" +
@@ -815,43 +931,61 @@ calDavCalendar.prototype = {
 
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_REMOVE_ERROR,
                                             status,
                                             responseBody);
             }
         };
         var delListener2 = {};
         delListener2.onStreamComplete =
-        function caldav_dDI_del2_onStreamComplete(aLoader, aContext, aStatus, aResultLength, aResult) {
-            let request = aLoader.request.QueryInterface(Components.interfaces.nsIHttpChannel);
-            let status = request.responseStatus;
-            if (status == 404) {
-                // someone else already deleted it
-                return;
-            } else {
-                thisCalendar.promptOverwrite(CALDAV_DELETE_ITEM, aItem,
-                                             realListener, null);
-            }
-        };
+            function caldav_dDI_del2_onStreamComplete(aLoader, aContext, aStatus, aResultLength, aResult) {
+                let request = aLoader.request.QueryInterface(Components.interfaces.nsIHttpChannel);
+                let status = request.responseStatus;
+                if (status == 404) {
+                    // someone else already deleted it
+                    return;
+                } else if ((status >= 500 && status <= 510) && (useCache && thisCalendar.mOfflineStorage)) {
+                    cal.LOG("[calDavCalendar] doDeleteItemOrUseCache recd status response of remote calendar unavailability. Putting entry in cache for a later try.");
+                    let opListener = {
+                         //We should not return a success code since the listeners can delete the physical item in case of success
+                         onGetResult: function(calendar, status, itemType, detail, count, items) {
+                        },
+                         onOperationComplete: function(calendar, status, opType, id, detail) {
+                              aListener.onOperationComplete(calendar, Components.results.NS_ERROR_CONNECTION_REFUSED,
+                                               Components.interfaces.calIOperationListener.GET, aItem.id, aItem);
+                        }
+                    };
+                    thisCalendar.deleteOfflineItem(aItem, opListener);
+                } else {
+                    thisCalendar.promptOverwrite(CALDAV_DELETE_ITEM, aItem,
+                                                 realListener, null);
+                }
+            };
 
         if (this.verboseLogging()) {
             cal.LOG("CalDAV: Deleting " + eventUri.spec);
         }
 
         let httpchannel = cal.prepHttpChannel(eventUri, null, null, this);
         if (!aIgnoreEtag) {
             httpchannel.setRequestHeader("If-Match",
                                          this.mItemInfoCache[aItem.id].etag,
                                          false);
         }
         httpchannel.requestMethod = "DELETE";
 
         cal.sendHttpRequest(cal.createStreamLoader(), httpchannel, delListener);
     },
 
+    deleteOfflineItem: function(item, listener) {
+        /* We do not delete the item from the cache, as we will need it when reconciling the cache content and the server content. */
+        let storage = this.mOfflineStorage.QueryInterface(Components.interfaces.calIOfflineStorage);
+        storage.deleteOfflineItem(item, listener);
+    },
+
     /**
      * Add an item to the target calendar
      *
      * @param path      Item path MUST NOT BE ENCODED
      * @param calData   iCalendar string representation of the item
      * @param aUri      Base URI of the request
      * @param aListener Listener
      */
@@ -899,38 +1033,38 @@ calDavCalendar.prototype = {
         // uri's path has. This way we make sure to handle servers
         // that pass paths like /dav/user/Calendar while
         // the request uri is like /dav/user@example.org/Calendar.
         let resPathComponents = path.split("/");
         resPathComponents.splice(0, uriPathComponentLength - 1);
         let locationPath = resPathComponents.join("/");
         let isInboxItem = this.isInbox(aUri.spec);
 
-        if (this.mPathIndex[path] &&
+        if (this.mHrefIndex[path] &&
             !this.mItemInfoCache[item.id]) {
             // If we get here it means a meeting has kept the same filename
             // but changed its uid, which can happen server side.
             // Delete the meeting before re-adding it
             this.deleteTargetCalendarItem(path);
         }
 
         if (this.mItemInfoCache[item.id]) {
             this.mItemInfoCache[item.id].isNew = false;
         } else {
             this.mItemInfoCache[item.id] = { isNew: true };
         }
         this.mItemInfoCache[item.id].locationPath = locationPath;
         this.mItemInfoCache[item.id].isInboxItem = isInboxItem;
 
-        this.mPathIndex[path] = item.id;
+        this.mHrefIndex[path] = item.id;
         this.mItemInfoCache[item.id].etag = etag;
         if (this.mItemInfoCache[item.id].isNew) {
-            this.mTargetCalendar.adoptItem(item, aListener);
+            this.mOfflineStorage.adoptItem(item, aListener);
         } else {
-            this.mTargetCalendar.modifyItem(item, null, aListener);
+            this.mOfflineStorage.modifyItem(item, null, aListener);
         }
 
         if (this.isCached) {
             this.setMetaData(item.id, path, etag, isInboxItem);
         }
     },
 
     /**
@@ -949,29 +1083,32 @@ calDavCalendar.prototype = {
                                                      aCount,
                                                      aItems) {
 
                 foundItem = aItems[0];
             },
             onOperationComplete: function deleteLocalItem_getItem_onOperationComplete() {}
         };
 
-        this.mTargetCalendar.getItem(this.mPathIndex[path],
+        this.mOfflineStorage.getItem(this.mHrefIndex[path],
                                      getItemListener);
         // Since the target calendar's operations are synchronous, we can
         // safely set variables from this function.
         if (foundItem) {
             let wasInboxItem = this.mItemInfoCache[foundItem.id].isInboxItem;
             if ((wasInboxItem && this.isInbox(path)) ||
                 (wasInboxItem === false && !this.isInbox(path))) {
 
                 cal.LOG("CalDAV: deleting item: " + path + ", uid: " + foundItem.id);
-                delete this.mPathIndex[path];
+                delete this.mHrefIndex[path];
                 delete this.mItemInfoCache[foundItem.id];
-                this.mTargetCalendar.deleteItem(foundItem,
+                if (this.isCached) {
+                    this.mOfflineStorage.deleteMetaData(foundItem.id);
+                }
+                this.mOfflineStorage.deleteItem(foundItem,
                                                 getItemListener);
                 isDeleted = true;
             }
         }
         return isDeleted;
     },
 
     /**
@@ -994,18 +1131,18 @@ calDavCalendar.prototype = {
             }
         } else {
             this.mObservers.notify("onLoad", [this]);
         }
 
         this.mFirstRefreshDone = true;
         while (this.mQueuedQueries.length) {
             let query = this.mQueuedQueries.pop();
-            this.mTargetCalendar.getItems
-                .apply(this.mTargetCalendar, query);
+            this.mOfflineStorage.getItems
+                .apply(this.mOfflineStorage, query);
         }
         if (this.hasScheduling &&
             !this.isInbox(calendarURI.spec)) {
             this.pollInbox();
         }
     },
 
     /**
@@ -1013,21 +1150,16 @@ calDavCalendar.prototype = {
      *
      * @param errorMsg           Error message
      * @param aListener          (optional) Listener of the request
      * @param aChangeLogListener (optional)Listener for cached calendars
      */
     notifyGetFailed: function notifyGetFailed(errorMsg, aListener, aChangeLogListener) {
          cal.WARN("CalDAV: Get failed: " + errorMsg);
 
-         // Revert the ctag, since something failed it is no longer valid.
-         this.mCtag = this.mOldCtag;
-         this.mTargetCalendar.setMetaData("ctag", this.mCtag);
-         this.mOldCtag = null;
-
          // Notify changelog listener
          if (this.isCached && aChangeLogListener) {
              aChangeLogListener.onResult({ status: Components.results.NS_ERROR_FAILURE },
                                          Components.results.NS_ERROR_FAILURE);
          }
 
          // Notify operation listener
          this.notifyOperationComplete(aListener,
@@ -1078,39 +1210,39 @@ calDavCalendar.prototype = {
                                                null,
                                                aListener,
                                                aChangeLogListener);
         multiget.doMultiGet();
     },
 
     // void getItem( in string id, in calIOperationListener aListener );
     getItem: function caldav_getItem(aId, aListener) {
-        this.mTargetCalendar.getItem(aId, aListener);
+        this.mOfflineStorage.getItem(aId, aListener);
     },
 
     // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
     //                in calIOperationListener aListener );
     getItems: function caldav_getItems(aItemFilter, aCount, aRangeStart,
                                        aRangeEnd, aListener) {
         if (this.isCached) {
-            if (this.mTargetCalendar) {
-                this.mTargetCalendar.getItems.apply(this.mTargetCalendar, arguments);
+            if (this.mOfflineStorage) {
+                this.mOfflineStorage.getItems.apply(this.mOfflineStorage, arguments);
             } else {
                 this.notifyOperationComplete(aListener,
                                              Components.results.NS_OK,
                                              Components.interfaces.calIOperationListener.GET,
                                              null,
                                              null);
             }
         } else {
             if (!this.mCheckedServerInfo) {
                 this.mQueuedQueries.push(arguments);
             } else {
-                this.mTargetCalendar.getItems.apply(this.mTargetCalendar, arguments);
+                this.mOfflineStorage.getItems.apply(this.mOfflineStorage, arguments);
             }
         }
     },
 
     safeRefresh: function caldav_safeRefresh(aChangeLogListener) {
         this.ensureTargetCalendar();
 
         if (this.mAuthScheme == "Digest") {
@@ -1214,19 +1346,18 @@ calDavCalendar.prototype = {
                                                 Components.results.NS_OK);
                 }
                 return;
             }
 
             var ctag = multistatus..CS::getctag.toString();
             if (!ctag.length || ctag != thisCalendar.mCtag) {
                 // ctag mismatch, need to fetch calendar-data
-                thisCalendar.mOldCtag = thisCalendar.mCtag;
                 thisCalendar.mCtag = ctag;
-                thisCalendar.mTargetCalendar.setMetaData("ctag", ctag);
+                thisCalendar.saveCalendarProperties();
                 thisCalendar.getUpdatedItems(thisCalendar.calendarUri,
                                              aChangeLogListener);
                 if (thisCalendar.verboseLogging()) {
                     cal.LOG("CalDAV: ctag mismatch on refresh, fetching data for " +
                             "calendar " + thisCalendar.name);
 
                 }
             } else {
@@ -1245,23 +1376,17 @@ calDavCalendar.prototype = {
                     thisCalendar.pollInbox();
                 }
             }
         };
         cal.sendHttpRequest(cal.createStreamLoader(), httpchannel, streamListener);
     },
 
     refresh: function caldav_refresh() {
-        if (!this.mCheckedServerInfo) {
-            // If we haven't refreshed yet, then we should check the resource
-            // type first. This will call refresh() again afterwards.
-            this.checkDavResourceType(null);
-        } else {
-            this.safeRefresh();
-        }
+        this.replayChangesOn(null);
     },
 
     firstInRealm: function caldav_firstInRealm() {
         var calendars = getCalendarManager().getCalendars({});
         for (var i = 0; i < calendars.length ; i++) {
             if (calendars[i].type != "caldav") {
                 continue;
             }
@@ -1450,32 +1575,31 @@ calDavCalendar.prototype = {
                 thisCalendar.completeCheckServerInfo(aChangeLogListener,
                                                      Components.interfaces.calIErrors.DAV_NOT_DAV);
                 return;
             }
 
             // check for webdav-sync capability
             // http://tools.ietf.org/html/draft-daboo-webdav-sync
             if (multistatus..D::["supported-report-set"]..D::["sync-collection"].length() > 0) {
-                LOG("CalDAV: Collection has webdav sync support");
+                cal.LOG("CalDAV: Collection has webdav sync support");
                 thisCalendar.mHasWebdavSyncSupport = true;
             }
 
             // check for server-side ctag support only if webdav sync is not available
             var ctag = multistatus..CS::["getctag"].toString();
             if (!thisCalendar.mHasWebdavSyncSupport && ctag.length) {
                 // We compare the stored ctag with the one we just got, if
                 // they don't match, we update the items in safeRefresh.
                 if (ctag == thisCalendar.mCtag) {
                     thisCalendar.mFirstRefreshDone = true;
                 }
 
                 thisCalendar.mCtag = ctag;
-                thisCalendar.mOldCtag = null;
-                thisCalendar.mTargetCalendar.setMetaData("ctag", ctag);
+                thisCalendar.saveCalendarProperties();
                 if (thisCalendar.verboseLogging()) {
                     cal.LOG("CalDAV: initial ctag " + ctag + " for calendar " +
                             thisCalendar.name);
                 }
             }
 
             supportedComponentsXml = multistatus..C::["supported-calendar-component-set"];
             // use supported-calendar-component-set if the server supports it; some do not
@@ -1902,16 +2026,17 @@ calDavCalendar.prototype = {
      * checkServerCaps
      * findPrincipalNS
      * checkPrincipalsNameSpace
      * completeCheckServerInfo                      * You are here
      */
     completeCheckServerInfo: function caldav_completeCheckServerInfo(aChangeLogListener, aError) {
         if (Components.isSuccessCode(aError)) {
             // "undefined" is a successcode, so all is good
+            this.saveCalendarProperties();
             this.mCheckedServerInfo = true;
             this.setProperty("currentStatus", Components.results.NS_OK);
 
             if (this.isCached) {
                 this.safeRefresh(aChangeLogListener);
             } else {
                 this.refresh();
             }
@@ -2269,38 +2394,38 @@ calDavCalendar.prototype = {
                 var att = newItem.getAttendeeById(attendee.id);
                 if (att) {
                     newItem.removeAttendee(att);
                     att = att.clone();
                     att.participationStatus = attendee.participationStatus;
                     newItem.addAttendee(att);
                 }
             }
-            thisCalendar.doModifyItem(newItem, itemToUpdate.parentItem /* related to bug 396182 */,
-                                      modListener, true);
+            thisCalendar.doModifyItemOrUseCache(newItem, itemToUpdate.parentItem /* related to bug 396182 */,
+                                      true, modListener, true);
         };
 
         var modListener = {};
         modListener.onOperationComplete = function caldav_pIR_moOC(aCalendar,
                                                                    aStatus,
                                                                    aOperationType,
                                                                    aItemId,
                                                                    aDetail) {
             cal.LOG("CalDAV: status " + aStatus + " while processing iTIP REPLY " +
                     " for " + thisCalendar.name);
             // don't delete the REPLY item from inbox unless modifying the master
             // item was successful
             if (aStatus == 0) { // aStatus undocumented; 0 seems to indicate no error
                 var delUri = thisCalendar.calendarUri.clone();
                 delUri.path = thisCalendar.ensureEncodedPath(aPath);
-                thisCalendar.doDeleteItem(aItem, null, true, true, delUri);
+                thisCalendar.doDeleteItemOrUseCache(aItem, true, null, true, true, delUri);
             }
         };
 
-        this.mTargetCalendar.getItem(aItem.id, getItemListener);
+        this.mOfflineStorage.getItem(aItem.id, getItemListener);
     },
 
     canNotify: function caldav_canNotify(aMethod, aItem) {
         if (this.hasAutoScheduling) {
             // canNotify should return false if the schedule agent is client
             // so the itip transport(imip) takes care of notifying participants
             if (aItem.organizer &&
                 aItem.organizer.getProperty("SCHEDULE-AGENT") == "CLIENT") {
@@ -2490,17 +2615,16 @@ calDavCalendar.prototype = {
                             uploadContent,
                             this,
                             aNewChannel);
 
         // Make sure we can get/set headers on both channels.
         aNewChannel.QueryInterface(Components.interfaces.nsIHttpChannel);
         aOldChannel.QueryInterface(Components.interfaces.nsIHttpChannel);
 
-
         function copyHeader(aHdr) {
             try {
                 let hdrValue = aOldChannel.getRequestHeader(aHdr);
                 if (hdrValue) {
                     aNewChannel.setRequestHeader(aHdr, hdrValue, false);
                 }
             } catch(e) {
                 if (e.code != Components.results.NS_ERROR_NOT_AVAILIBLE) {
--- a/calendar/providers/caldav/calDavRequestHandlers.js
+++ b/calendar/providers/caldav/calDavRequestHandlers.js
@@ -130,17 +130,17 @@ etagsHandler.prototype = {
 
         // Now that we are done, check which items need fetching.
         if (this.calendar.isCached) {
             this.calendar.superCalendar.startBatch();
         }
 
         let needsRefresh = false;
         try {
-            for (let path in this.calendar.mPathIndex) {
+            for (let path in this.calendar.mHrefIndex) {
                 if (path in this.itemsReported ||
                     path.substr(0, this.baseUri.length) == this.baseUri) {
                     // If the item is also on the server, check the next.
                     continue;
                 }
                 // If an item has been deleted from the server, delete it here too.
                 // Since the target calendar's operations are synchronous, we can
                 // safely set variables from this function.
@@ -152,25 +152,25 @@ etagsHandler.prototype = {
                                                                  aDetail,
                                                                  aCount,
                                                                  aItems) {
                         foundItem = aItems[0];
                     },
                     onOperationComplete: function etags_getItem_onOperationComplete() {}
                 };
 
-                this.calendar.mTargetCalendar.getItem(this.calendar.mPathIndex[path],
+                this.calendar.mOfflineStorage.getItem(this.calendar.mHrefIndex[path],
                                                       getItemListener);
                 if (foundItem) {
                     let wasInboxItem = this.calendar.mItemInfoCache[foundItem.id].isInboxItem;
                     if ((wasInboxItem && this.calendar.isInbox(this.baseUri.spec)) ||
                         (wasInboxItem === false && !this.calendar.isInbox(this.baseUri.spec))) {
                         cal.LOG("Deleting local href: " + path)
-                        delete this.calendar.mPathIndex[path];
-                        this.calendar.mTargetCalendar.deleteItem(foundItem, null);
+                        delete this.calendar.mHrefIndex[path];
+                        this.calendar.mOfflineStorage.deleteItem(foundItem, null);
                         needsRefresh = true;
                     }
                 }
             }
         } finally {
             if (this.calendar.isCached) {
                 this.calendar.superCalendar.endBatch();
             }
@@ -282,17 +282,17 @@ etagsHandler.prototype = {
                         r.getcontenttype = "text/calendar";
                     }
 
                     // Only handle calendar items
                     if (r.getcontenttype.substr(0,13) == "text/calendar") {
                         if (r.href && r.href.length) {
                             this.itemsReported[r.href] = r.getetag;
 
-                            let itemUid = this.calendar.mPathIndex[r.href];
+                            let itemUid = this.calendar.mHrefIndex[r.href];
                             if (!itemUid ||
                                 r.getetag != this.calendar.mItemInfoCache[itemUid].etag) {
                                 this.itemsNeedFetching.push(r.href);
                             }
                         }
                     }
                 }
                 break;
@@ -422,18 +422,18 @@ webDavSyncHandler.prototype = {
         }
         // Invalidate sync token with 4xx errors that could indicate the
         // sync token has become invalid and do a refresh
         else if (this.calendar.mWebdavSyncToken != null &&
                  responseStatus >= 400 &&
                  responseStatus <= 499) {
             cal.LOG("CalDAV: Reseting sync token because server returned status code: " + responseStatus);
             this._reader = null;
-            this.calendar.mWebdavSyncToken=null;
-            this.calendar.mTargetCalendar.deleteMetaData("webdav-sync-token");
+            this.calendar.mWebdavSyncToken = null;
+            this.calendar.saveCalendarProperties();
             this.calendar.safeRefresh(this.changeLogListener);
         } else {
             cal.WARN("CalDAV: Error doing webdav sync: " + responseStatus);
             this.calendar.reportDavError(Components.interfaces.calIErrors.DAV_REPORT_ERROR);
             if (this.calendar.isCached && this.changeLogListener) {
                 this.changeLogListener.onResult({ status: Components.results.NS_ERROR_FAILURE },
                                                 Components.results.NS_ERROR_FAILURE);
             }
@@ -500,31 +500,30 @@ webDavSyncHandler.prototype = {
             }
             return;
         }
 
         if (this.calendar.mWebdavSyncToken == null) {
             // null token means reset or first refresh indicating we did
             // a full sync; remove local items that were not returned in this full
             // sync
-            for (let path in this.calendar.mPathIndex) {
+            for (let path in this.calendar.mHrefIndex) {
                 if (!this.itemsReported[path]) {
                     this.calendar.deleteTargetCalendarItem(path);
                 }
             }
         }
         if (this.calendar.isCached) {
             this.calendar.superCalendar.endBatch();
         }
 
         if (!this.itemsNeedFetching.length) {
             if (this.newSyncToken) {
                 this.calendar.mWebdavSyncToken = this.newSyncToken;
-                this.calendar.mTargetCalendar.setMetaData("webdav-sync-token",
-                                                          this.newSyncToken);
+                this.calendar.saveCalendarProperties();
                 cal.LOG("CalDAV: New webdav-sync Token: " + this.calendar.mWebdavSyncToken);
             }
             this.calendar.finalizeUpdatedItems(this.changeLogListener,
                                                this.baseUri);
         } else {
             let multiget = new multigetSyncHandler(this.itemsNeedFetching,
                                                    this.calendar,
                                                    this.baseUri,
@@ -578,34 +577,34 @@ webDavSyncHandler.prototype = {
                     r.href.length) {
                     r.href = this.calendar.ensureDecodedPath(r.href);
                 }
                 // Deleted item
                 if (r.href && r.href.length &&
                     r.status &&
                     r.status.length &&
                     r.status.indexOf(" 404") > 0) {
-                    if (this.calendar.mPathIndex[r.href]) {
+                    if (this.calendar.mHrefIndex[r.href]) {
                         this.changeCount++;
                         this.calendar.deleteTargetCalendarItem(r.href);
                     }
                     else {
                         cal.LOG("CalDAV: skipping unfound deleted item : " + r.href);
                     }
                 // Only handle Created or Updated calendar items
                 } else if (r.getcontenttype &&
                            r.getcontenttype.substr(0,13) == "text/calendar" &&
                            r.getetag && r.getetag.length &&
                            r.href && r.href.length &&
                            (!r.status ||                 // Draft 3 does not require
                             r.status.length == 0 ||      // a status for created or updated items but
                             r.status.indexOf(" 204") ||  // draft 0, 1 and 2 needed it so treat no status
                             r.status.indexOf(" 201"))) { // and status 201 and 204 the same
                     this.itemsReported[r.href] = r.getetag;
-                    let itemId = this.calendar.mPathIndex[r.href];
+                    let itemId = this.calendar.mHrefIndex[r.href];
                     let oldEtag = (itemId && this.calendar.mItemInfoCache[itemId].etag);
 
                     if (!oldEtag || oldEtag != r.getetag) {
                         // Etag mismatch, getting new/updated item.
                         this.itemsNeedFetching.push(r.href);
                     }
                 // If the response element is still not handled, log an error
                 // only if the content-type is text/calendar or the
@@ -765,17 +764,17 @@ multigetSyncHandler.prototype = {
         if (this.unhandledErrors) {
             this.calendar.superCalendar.endBatch();
             this.calendar.notifyGetFailed("multiget error", this.listener, this.changeLogListener);
             return;
         }
         if (this.itemsNeedFetching.length == 0) {
             if (this.newSyncToken) {
                 this.calendar.mWebdavSyncToken = this.newSyncToken;
-                this.calendar.mTargetCalendar.setMetaData("webdav-sync-token", this.newSyncToken);
+                this.calendar.saveCalendarProperties();
               cal.LOG("CalDAV: New webdav-sync Token: " + this.calendar.mWebdavSyncToken);
             }
 
             this.calendar.finalizeUpdatedItems(this.changeLogListener,
                                                this.baseUri);
         }
         if (!this._reader) {
             // No reader means there was a request error. The error is already
@@ -889,28 +888,28 @@ multigetSyncHandler.prototype = {
                 if (r.href &&
                     r.href.length) {
                     r.href = this.calendar.ensureDecodedPath(r.href);
                 }
                 if (r.href && r.href.length &&
                     r.status &&
                     r.status.length &&
                     r.status.indexOf(" 404") > 0) {
-                    if (this.calendar.mPathIndex[r.href]) {
+                    if (this.calendar.mHrefIndex[r.href]) {
                         this.changeCount++;
                         this.calendar.deleteTargetCalendarItem(r.href);
                     } else {
                         cal.LOG("CalDAV: skipping unfound deleted item : " + r.href);
                     }
                 // Created or Updated item
                 } else if (r.getetag && r.getetag.length &&
                            r.href && r.href.length &&
                            r.calendardata && r.calendardata.length) {
                     let oldEtag;
-                    let itemId = this.calendar.mPathIndex[r.href];
+                    let itemId = this.calendar.mHrefIndex[r.href];
                     if (itemId) {
                         oldEtag = this.calendar.mItemInfoCache[itemId].etag;
                     } else {
                         oldEtag = null;
                     }
                     if (!oldEtag || oldEtag != r.getetag) {
                         this.changeCount++;
                         this.calendar.addTargetCalendarItem(r.href,
--- a/calendar/providers/ics/calICSCalendar.js
+++ b/calendar/providers/ics/calICSCalendar.js
@@ -242,16 +242,19 @@ calICSCalendar.prototype = {
         } else {
             // Failure may be due to temporary connection issue, keep old data to
             // prevent potential data loss if it becomes available again.
             cal.LOG("[calICSCalendar] Unable to load stream - status: " + status);
         }
 
         if (!cont) {
             // no need to process further, we can use the previous data
+            // HACK Sorry, but offline support requires the items to be signaled
+            // even if nothing has changed (especially at startup)
+            this.mObserver.onLoad(this);
             this.unlock();
             return;
         }
 
          // Clear any existing events if there was no result
         if (!resultLength) {
             this.createMemoryCalendar();
             this.mMemoryCalendar.addObserver(this.mObserver);
--- a/calendar/providers/memory/calMemoryCalendar.js
+++ b/calendar/providers/memory/calMemoryCalendar.js
@@ -44,32 +44,34 @@ Components.utils.import("resource://cale
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 //
 // calMemoryCalendar.js
 //
 
 const calCalendarManagerContractID = "@mozilla.org/calendar/manager;1";
 const calICalendarManager = Components.interfaces.calICalendarManager;
+const cICL = Components.interfaces.calIChangeLog;
 
 function calMemoryCalendar() {
     this.initProviderBase();
     this.initMemoryCalendar();
 }
 
 calMemoryCalendar.prototype = {
     __proto__: cal.ProviderBase.prototype,
 
     classID: Components.ID("{bda0dd7f-0a2f-4fcf-ba08-5517e6fbf133}"),
     contractID: "@mozilla.org/calendar/calendar;1?type=memory",
     classDescription: "Calendar Memory Provider",
 
     getInterfaces: function getInterfaces(count) {
         const ifaces = [Components.interfaces.calICalendar,
                         Components.interfaces.calISchedulingSupport,
+                        Components.interfaces.calIOfflineStorage,
                         Components.interfaces.calISyncWriteCalendar,
                         Components.interfaces.calICalendarProvider,
                         Components.interfaces.nsIClassInfo,
                         Components.interfaces.nsISupports];
         count.value = ifaces.length;
         return ifaces;
     },
     getHelperForLanguage: function getHelperForLanguage(language) {
@@ -80,19 +82,25 @@ calMemoryCalendar.prototype = {
 
     //
     // nsISupports interface
     // 
     QueryInterface: function (aIID) {
         return cal.doQueryInterface(this, calMemoryCalendar.prototype, aIID, null, this);
     },
 
+    mItems: null,
+    mOfflineFlags: null,
+    mObservers: null,
+    mMetaData: null,
+
     initMemoryCalendar: function() {
         this.mObservers = new cal.ObserverBag(Components.interfaces.calIObserver);
         this.mItems = {};
+        this.mOfflineFlags = {};
         this.mMetaData = new cal.calPropertyBag();
     },
 
     //
     // calICalendarProvider interface
     //
     get prefChromeOverlay() {
         return null;
@@ -157,29 +165,33 @@ calMemoryCalendar.prototype = {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.ADD,
                                          aItem.id,
                                          "Can't set ID on non-mutable item to addItem");
             return;
         }
 
-        if (this.mItems[aItem.id] != null) {
+        //Lines below are commented because of the offline bug 380060
+        //Memory Calendar cannot assume that a new item should not have an ID.
+        //calCachedCalendar could send over an item with an id.
+
+        /*if (this.mItems[aItem.id] != null) {
             if (this.relaxedMode) {
                 // we possibly want to interact with the user before deleting
                 delete this.mItems[aItem.id];
             } else {
                 this.notifyOperationComplete(aListener,
                                              Components.interfaces.calIErrors.DUPLICATE_ID,
                                              Components.interfaces.calIOperationListener.ADD,
                                              aItem.id,
                                              "ID already exists for addItem");
                 return;
             }
-        }
+        }*/
 
         let parentItem = aItem.parentItem;
         if (parentItem != aItem) {
             parentItem = parentItem.clone();
             parentItem.recurrenceInfo.modifyException(aItem, true);
         }
         parentItem.calendar = this.superCalendar;
 
@@ -219,39 +231,50 @@ calMemoryCalendar.prototype = {
             return reportError(null, "ID for modifyItem item is null");
         }
 
         var modifiedItem = aNewItem.parentItem.clone();
         if (aNewItem.parentItem != aNewItem) {
             modifiedItem.recurrenceInfo.modifyException(aNewItem, false);
         }
 
+        // If no old item was passed, then we should overwrite in any case.
+        // Pick up the old item from our items array and use this as an old item
+        // later on.
+        if (!aOldItem) {
+            aOldItem = this.mItems[aNewItem.id];
+        }
+
         if (this.relaxedMode) {
+            // We've already filled in the old item above, if this doesn't exist
+            // then just take the current item as its old version
             if (!aOldItem) {
-                aOldItem = (this.mItems[aNewItem.id] || modifiedItem);
+                aOldItem = modifiedItem;
             }
             aOldItem = aOldItem.parentItem;
-        } else {
+        } else if (!this.relaxedMode) {
             if (!aOldItem || !this.mItems[aNewItem.id]) {
                 // no old item found?  should be using addItem, then.
                 return reportError("ID for modifyItem doesn't exist, is null, or is from different calendar");
             }
 
             // do the old and new items match?
             if (aOldItem.id != modifiedItem.id) {
                 return reportError("item ID mismatch between old and new items");
             }
 
             aOldItem = aOldItem.parentItem;
             var storedOldItem = this.mItems[aOldItem.id];
 
             // compareItems is not suitable here. See bug 418805.
+            // Cannot compare here due to bug 380060
             if (!cal.compareItemContent(storedOldItem, aOldItem)) {
-                return reportError("old item mismatch in modifyItem");
+                return reportError("old item mismatch in modifyItem" + " storedId:" + storedOldItem.icalComponent + " old item:" + aOldItem.icalComponent);
             }
+           // offline bug
 
             if (aOldItem.generation != storedOldItem.generation) {
                 return reportError("generation mismatch in modifyItem");
             }
 
             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.
@@ -435,27 +458,43 @@ calMemoryCalendar.prototype = {
             } else if (wantTodos) {
                 typeIID = Components.interfaces.calITodo;
             }
         }
 
         aRangeStart = cal.ensureDateTime(aRangeStart);
         aRangeEnd = cal.ensureDateTime(aRangeEnd);
 
+
+        let offline_filter = aItemFilter &
+            (calICalendar.ITEM_FILTER_OFFLINE_DELETED |
+             calICalendar.ITEM_FILTER_OFFLINE_CREATED |
+             calICalendar.ITEM_FILTER_OFFLINE_MODIFIED);
+
         for (let itemIndex in this.mItems) {
             let item = this.mItems[itemIndex];
             let isEvent_ = cal.isEvent(item);
             if (isEvent_) {
                 if (!wantEvents) {
                     continue;
                 }
             } else if (!wantTodos) {
                 continue;
             }
 
+            let hasItemFlag = (item.id in this.mOfflineFlags);
+            let offlineFlag = (hasItemFlag ? this.mOfflineFlags[item.id] : null);
+
+            // If the offline flag doesn't match, skip the item
+            if ((hasItemFlag ||
+                    (offline_filter != 0 && offlineFlag == cICL.OFFLINE_FLAG_DELETED_RECORD)) &&
+                (offlineFlag != offline_filter)) {
+                continue;
+            }
+
             if (itemReturnOccurrences && item.recurrenceInfo) {
                 let startDate  = aRangeStart;
                 if (!aRangeStart && cal.isToDo(item)) {
                     startDate = item.entryDate;
                 }
                 let occurrences = item.recurrenceInfo.getOccurrences(
                     startDate, aRangeEnd, aCount ? aCount - itemsFound.length : 0, {});
                 if (wantUnrespondedInvitations) {
@@ -487,16 +526,74 @@ calMemoryCalendar.prototype = {
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.GET,
                                      null,
                                      null);
     },
 
     //
+    // calIOfflineStorage interface
+    //
+    addOfflineItem: function addOfflineItem(aItem, aListener) {
+        this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_CREATED_RECORD;
+        this.notifyOperationComplete(aListener,
+                                     Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.ADD,
+                                     aItem.id,
+                                     aItem);
+    },
+
+    modifyOfflineItem: function modifyOfflineItem(aItem, aListener) {
+        let oldFlag = this.mOfflineFlags[aItem.id];
+        if (oldFlag != cICL.OFFLINE_FLAG_CREATED_RECORD &&
+            oldFlag != cICL.OFFLINE_FLAG_DELETED_RECORD) {
+            this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
+        }
+
+        this_.notifyOperationComplete(aListener,
+                                      Components.results.NS_OK,
+                                      Components.interfaces.calIOperationListener.MODIFY,
+                                      aItem.id,
+                                      aItem);
+    },
+
+    deleteOfflineItem: function deleteOfflineItem(aItem, aListener) {
+        let oldFlag = this.mOfflineFlags[aItem.id];
+        if (oldFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
+            delete this.mItems[aItem.id];
+            delete this.mOfflineFlags[aItem.id];
+        } else {
+            this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_DELETED_RECORD;
+        }
+
+        this.notifyOperationComplete(aListener,
+                                     Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.DELETE,
+                                     aItem.id,
+                                     aItem);
+        // notify observers
+        this_.observers.notify("onDeleteItem", [aItem]);
+    },
+
+    getItemOfflineFlag: function getItemOfflineFlag(aItem, aListener) {
+        let flag = (aItem && aItem.id in this.mOfflineFlags ? this.mOfflineFlags[aItem.id] : null);
+        this.notifyOperationComplete(aListener, Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.GET,
+                                     null, flag);
+    },
+
+    resetItemOfflineFlag: function resetItemOfflineFlag(aItem, aListener) {
+        delete this.mOfflineFlags[aItem.id];
+        this.notifyOperationComplete(aListener, Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.MODIFY,
+                                     aItem.id, aItem);
+    },
+
+    //
     // calISyncWriteCalendar interface
     //
     setMetaData: function memory_setMetaData(id, value) {
         this.mMetaData.setProperty(id, value);
     },
     deleteMetaData: function memory_deleteMetaData(id) {
         this.mMetaData.deleteProperty(id);
     },
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -49,16 +49,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 Components.utils.import("resource://calendar/modules/calStorageUpgrade.jsm");
 Components.utils.import("resource://calendar/modules/calStorageHelpers.jsm");
 
 const USECS_PER_SECOND = 1000000;
 const kCalICalendar = Components.interfaces.calICalendar;
+const cICL = Components.interfaces.calIChangeLog;
 
 //
 // calStorageCalendar
 //
 
 function calStorageCalendar() {
     this.initProviderBase();
     this.mItemCache = {};
@@ -85,44 +86,46 @@ calStorageCalendar.prototype = {
     },
 
     getHelperForLanguage: function (language) {
         return null;
     },
 
     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
     flags: 0,
+
     //
     // private members
     //
     mDB: null,
     mItemCache: null,
     mRecItemCacheInited: false,
     mRecEventCache: null,
     mRecTodoCache: null,
     mLastStatement: null,
 
     //
     // nsISupports interface
     //
     QueryInterface: function (aIID) {
-        return doQueryInterface(this, calStorageCalendar.prototype, aIID,
-                                [Components.interfaces.calICalendarProvider,
-                                 Components.interfaces.calISyncWriteCalendar]);
+        return cal.doQueryInterface(this, calStorageCalendar.prototype, aIID,
+                                   [Components.interfaces.calICalendarProvider,
+                                    Components.interfaces.calIOfflineStorage,
+                                    Components.interfaces.calISyncWriteCalendar]);
     },
 
     //
     // calICalendarProvider interface
     //
     get prefChromeOverlay() {
         return null;
     },
 
     get displayName() {
-        return calGetString("calendar", "storageName");
+        return cal.calGetString("calendar", "storageName");
     },
 
     createCalendar: function cSC_createCalendar() {
         throw NS_ERROR_NOT_IMPLEMENTED;
     },
 
     deleteCalendar: function cSC_deleteCalendar(aCalendar, listener) {
         aCalendar = aCalendar.wrappedJSObject;
@@ -257,17 +260,17 @@ calStorageCalendar.prototype = {
             localDB = Services.storage.openDatabase(localDB);
 
             // First, we need to check if this is from 0.9, i.e we need to
             // migrate from storage.sdb to local.sqlite.
             let storageSdb = Services.dirsvc.get("ProfD", Components.interfaces.nsILocalFile);
             storageSdb.append("storage.sdb");
             this.mDB = Services.storage.openDatabase(storageSdb);
             if (this.mDB.tableExists("cal_events")) {
-                cal.LOG("Storage: Migrating storage.sdb -> local.sqlite");
+                cal.LOG("[calStorageCalendar] Migrating storage.sdb -> local.sqlite");
                 upgradeDB(this.mDB); // upgrade schema before migating data
                 let attachStatement = createStatement(this.mDB, "ATTACH DATABASE :file_path AS local_sqlite");
                 try {
                     attachStatement.params.file_path = localDB.databaseFile.path;
                     attachStatement.execute();
                 } catch (exc) {
                     this.logError("prepareInitDB attachStatement.execute exception", exc);
                     throw exc;
@@ -355,17 +358,17 @@ calStorageCalendar.prototype = {
                 let path = this.uri.path;
                 let pos = path.indexOf("?id=");
 
                 if (pos != -1) {
                     // There is an "id" parameter in the uri. This calendar
                     // has not been migrated to using the uuid as its cal_id.
                     pos = this.uri.path.indexOf("?id=");
                     if (pos != -1) {
-                        cal.LOG("Storage: Migrating numeric cal_id to uuid");
+                        cal.LOG("[calStorageCalendar] Migrating numeric cal_id to uuid");
                         id = parseInt(path.substr(pos + 4), 10);
                         migrateTables(this.mDB, this.id, id);
 
                         // Now remove the id from the uri to make sure we don't do this
                         // again. Remeber the id, so we can recover in case something
                         // goes wrong.
                         this.setProperty("uri", "moz-storage-calendar://");
                         this.setProperty("old_calendar_id", id);
@@ -374,17 +377,17 @@ calStorageCalendar.prototype = {
                     } else {
                         this.mDB.rollbackTransaction();
                     }
                 } else {
                     // For some reason, the first storage calendar before the
                     // v19 upgrade has cal_id=0. If we still have a
                     // moz-profile-calendar here, then this is the one and we
                     // need to move all events with cal_id=0 to this id.
-                    cal.LOG("Storage: Migrating stray cal_id=0 calendar to uuid");
+                    cal.LOG("[calStorageCalendar] Migrating stray cal_id=0 calendar to uuid");
                     migrateTables(this.mDB, this.id, 0);
                     this.setProperty("uri", "moz-storage-calendar://");
                     this.setProperty("old_calendar_id", 0);
                     this.mDB.commitTransaction();
                 }
             } catch (exc) {
                 this.logError("prepareInitDB  moz-profile-calendar migration exception", exc);
                 this.mDB.rollbackTransaction();
@@ -438,17 +441,17 @@ calStorageCalendar.prototype = {
                                          Components.interfaces.calIOperationListener.ADD,
                                          null,
                                          "Calendar is readonly");
             return;
         }
 
         if (aItem.id == null) {
             // is this an error?  Or should we generate an IID?
-            aItem.id = getUUID();
+            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);
                 } else {
                     this.notifyOperationComplete(aListener,
@@ -478,55 +481,80 @@ calStorageCalendar.prototype = {
                                      aItem.id,
                                      aItem);
 
         // notify observers
         this.observers.notify("onAddItem", [aItem]);
     },
 
     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
+    // Actually uses doModifyItem
     modifyItem: function cSC_modifyItem(aNewItem, aOldItem, aListener) {
+        let this_ = this;
 
+        // HACK Just modifying the item would clear the offline flag, we need to
+        // retrieve the flag and pass it to the real modify function.
+        let offlineJournalFlagListener = {
+            onGetResult: function (calendar, status, opType, id, detail) {
+            },
+            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 oldOfflineFlag = offlineFlag;
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.MODIFY,
                                          null,
                                          "Calendar is readonly");
             return null;
         }
         if (!aNewItem) {
             throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
-        var this_ = this;
+        let this_ = this;
         function reportError(errStr, errId) {
             this_.notifyOperationComplete(aListener,
                                           errId ? errId : Components.results.NS_ERROR_FAILURE,
                                           Components.interfaces.calIOperationListener.MODIFY,
                                           aNewItem.id,
                                           errStr);
             return null;
         }
 
         if (aNewItem.id == null) {
             // this is definitely an error
             return reportError("ID for modifyItem item is null");
         }
 
         // Ensure that we're looking at the base item if we were given an
         // occurrence.  Later we can optimize this.
-        var modifiedItem = aNewItem.parentItem.clone();
+        let modifiedItem = aNewItem.parentItem.clone();
         if (aNewItem.parentItem != aNewItem) {
             modifiedItem.recurrenceInfo.modifyException(aNewItem, false);
         }
 
+        // If no old item was passed, then we should overwrite in any case.
+        // Pick up the old item from the database and use this as an old item
+        // later on.
+        if (!aOldItem) {
+            aOldItem = this.getItemById(aNewItem.id);
+        }
+
         if (this.relaxedMode) {
+            // We've already filled in the old item above, if this doesn't exist
+            // then just take the current item as its old version
             if (!aOldItem) {
-                aOldItem = this.getItemById(aNewItem.id) || aNewItem;
+                aOldItem = aNewItem;
             }
             aOldItem = aOldItem.parentItem;
         } else {
             var storedOldItem = (aOldItem ? this.getItemById(aOldItem.id) : null);
             if (!aOldItem || !storedOldItem) {
                 // no old item found?  should be using addItem, then.
                 return reportError("ID does not already exist for modifyItem");
             }
@@ -543,16 +571,17 @@ calStorageCalendar.prototype = {
                 // 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.setOfflineJournalFlag(aNewItem, oldOfflineFlag);
 
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.MODIFY,
                                      modifiedItem.id,
                                      modifiedItem);
 
         // notify observers
@@ -610,21 +639,21 @@ calStorageCalendar.prototype = {
                                          Components.results.NS_OK,
                                          Components.interfaces.calIOperationListener.GET,
                                          aId,
                                          null);
             return;
         }
 
         var item_iid = null;
-        if (isEvent(item))
+        if (cal.isEvent(item)) {
             item_iid = Components.interfaces.calIEvent;
-        else if (isToDo(item))
+        } else if (cal.isToDo(item)) {
             item_iid = Components.interfaces.calITodo;
-        else {
+        } else {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.GET,
                                          aId,
                                          "Can't deduce item type based on QI");
             return;
         }
 
@@ -679,26 +708,37 @@ calStorageCalendar.prototype = {
         function checkUnrespondedInvitation(item) {
             var att = superCal.getInvitedAttendee(item);
             return (att && (att.participationStatus == "NEEDS-ACTION"));
         }
 
         var wantEvents = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_EVENT) != 0);
         var wantTodos = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_TODO) != 0);
         var asOccurrences = ((aItemFilter & kCalICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0);
+        var wantOfflineDeletedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_OFFLINE_DELETED) != 0);
+        var wantOfflineCreatedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_OFFLINE_CREATED) != 0);
+        var wantOfflineModifiedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_OFFLINE_MODIFIED) != 0);
+
         if (!wantEvents && !wantTodos) {
             // nothing to do
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_OK,
                                          Components.interfaces.calIOperationListener.GET,
                                          null,
                                          null);
             return;
         }
 
+        // HACK because recurring offline events/todos objects dont have offline_journal information
+        // Hence we need to update the mRecEventCacheOfflineFlags and  mRecTodoCacheOfflineFlags hash-tables
+        // It can be an expensive operation but is only used in Online Reconciliation mode
+        if (wantOfflineCreatedItems | wantOfflineDeletedItems | wantOfflineModifiedItems) {
+            this.mRecItemCacheInited = false;
+        }
+
         this.assureRecurringItemCaches();
 
         var itemCompletedFilter = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_YES) != 0);
         var itemNotCompletedFilter = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_NO) != 0);
 
         function checkCompleted(item) {
             return (item.isCompleted ? itemCompletedFilter : itemNotCompletedFilter);
         }
@@ -792,40 +832,51 @@ calStorageCalendar.prototype = {
             //
             try {
                 this.prepareStatement(this.mSelectNonRecurringEventsByRange);
                 sp = this.mSelectNonRecurringEventsByRange.params;
                 sp.range_start = startTime;
                 sp.range_end = endTime;
                 sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
                 sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
+                sp.offline_journal = null;
+
+                if (wantOfflineDeletedItems) sp.offline_journal = cICL.OFFLINE_FLAG_DELETED_RECORD;
+                else if (wantOfflineCreatedItems) sp.offline_journal = cICL.OFFLINE_FLAG_CREATED_RECORD;
+                else if (wantOfflineModifiedItems) sp.offline_journal = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
 
                 while (this.mSelectNonRecurringEventsByRange.step()) {
                     let row = this.mSelectNonRecurringEventsByRange.row;
                     resultItems.push(this.getEventFromRow(row, {}));
                 }
             } catch (e) {
                 this.logError("Error selecting non recurring events by range!\n", e);
             } finally {
                 this.mSelectNonRecurringEventsByRange.reset();
             }
 
-            // process the non-recurring events:
+            // Process the non-recurring events:
             for each (var evitem in resultItems) {
                 count += handleResultItem(evitem, Components.interfaces.calIEvent);
                 if (checkCount()) {
                     return;
                 }
             }
 
-            // process the recurring events from the cache
-            for each (var evitem in this.mRecEventCache) {
-                count += handleResultItem(evitem, Components.interfaces.calIEvent);
-                if (checkCount()) {
-                    return;
+            // Process the recurring events from the cache
+            for each (let evitem in this.mRecEventCache) {
+                let offline_journal_flag = this.mRecEventCacheOfflineFlags[evitem.id] || null;
+                // No need to return flagged unless asked i.e. sp.offline_journal == offline_journal_flag
+                // Return created and modified offline records if sp.offline_journal is null alongwith events that have no flag
+                if ((sp.offline_journal == null && offline_journal_flag != cICL.OFFLINE_FLAG_DELETED_RECORD)
+                    || (sp.offline_journal != null && offline_journal_flag == sp.offline_journal)) {
+                    count += handleResultItem(evitem, Components.interfaces.calIEvent);
+                    if (checkCount()) {
+                        return;
+                    }
                 }
             }
         }
 
         // if todos are wanted, do them next
         if (wantTodos) {
             var sp;             // stmt params
             var resultItems = [];
@@ -833,16 +884,20 @@ calStorageCalendar.prototype = {
             // first get non-recurring todos that happen to fall within the range
             try {
                 this.prepareStatement(this.mSelectNonRecurringTodosByRange);
                 sp = this.mSelectNonRecurringTodosByRange.params;
                 sp.range_start = startTime;
                 sp.range_end = endTime;
                 sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
                 sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
+                sp.offline_journal = null;
+                if (wantOfflineCreatedItems) sp.offline_journal = cICL.OFFLINE_FLAG_CREATED_RECORD;
+                if (wantOfflineDeletedItems) sp.offline_journal = cICL.OFFLINE_FLAG_DELETED_RECORD;
+                if (wantOfflineModifiedItems) sp.offline_journal = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
 
                 while (this.mSelectNonRecurringTodosByRange.step()) {
                     let row = this.mSelectNonRecurringTodosByRange.row;
                     resultItems.push(this.getTodoFromRow(row, {}));
                 }
             } catch (e) {
                 this.logError("Error selecting non recurring todos by range", e);
             } finally {
@@ -857,20 +912,31 @@ calStorageCalendar.prototype = {
                 }
             }
 
             // Note: Reading the code, completed *occurrences* seems to be broken, because
             //       only the parent item has been filtered; I fixed that.
             //       Moreover item.todo_complete etc seems to be a leftover...
 
             // process the recurring todos from the cache
-            for each (var todoitem in this.mRecTodoCache) {
-                count += handleResultItem(todoitem, Components.interfaces.calITodo, checkCompleted);
-                if (checkCount()) {
-                    return;
+            for each (let todoitem in this.mRecTodoCache) {
+                let offline_journal_flag = this.mRecTodoCacheOfflineFlags[todoitem.id] || null;
+                if ((sp.offline_journal == null &&
+                     (offline_journal_flag == cICL.OFFLINE_FLAG_MODIFIED_RECORD ||
+                      offline_journal_flag == cICL.OFFLINE_FLAG_CREATED_RECORD ||
+                      offline_journal_flag == null)) ||
+                    (sp.offline_journal != null &&
+                     (offline_journal_flag == sp.offline_journal))) {
+
+                    count += handleResultItem(todoitem,
+                                              Components.interfaces.calITodo,
+                                              checkCompleted);
+                    if (checkCount()) {
+                        return;
+                    }
                 }
             }
         }
 
         // flush the queue
         queueItems(null);
 
         // and finish
@@ -879,25 +945,166 @@ calStorageCalendar.prototype = {
                                      Components.interfaces.calIOperationListener.GET,
                                      null,
                                      null);
 
         //var profEndTime = Date.now();
         //dump ("++++ getItems took: " + (profEndTime - profStartTime) + " ms\n");
     },
 
+    getItemOfflineFlag: function cSC_getOfflineJournalFlag(aItem, aListener) {
+        let flag = null;
+        if (!aItem) {
+            // It is possible that aItem can be null, flag provided should be null in this case
+            aListener.onOperationComplete(this, Components.results.NS_OK,
+                                               Components.interfaces.calIOperationListener.GET, null, flag);
+        } else {
+            let aID = aItem.id;
+            let this_ = this;
+            let listener = {
+                handleResult: function(aResultSet) {
+                        let row = aResultSet.getNextRow();
+                        flag = row.getResultByName("offline_journal") || null;
+                },
+                handleError: function(aError) {
+                    this_.logError("Error getting offline flag", aError);
+                    aListener.onOperationComplete(this_, Components.results.NS_ERROR_FAILURE,
+                                                   Components.interfaces.calIOperationListener.GET, aItem.id, aItem);
+                },
+                handleCompletion: function(aReason) {
+                    aListener.onOperationComplete(this_, Components.results.NS_OK,
+                                                   Components.interfaces.calIOperationListener.GET, aItem.id, flag);
+                }
+            };
+            if (cal.isEvent(aItem)) {
+                this.prepareStatement(this.mSelectEvent);
+                this.mSelectEvent.params.id = aID;
+                this.mSelectEvent.statement.executeAsync(listener);
+            } else if (cal.isToDo(aItem)) {
+                this.prepareStatement(this.mSelectTodo);
+                this.mSelectTodo.params.id = aID;
+                this.mSelectTodo.statement.executeAsync(listener);
+            }
+        }
+    },
+
+    setOfflineJournalFlag: function cSC_setOfflineJournalFlag(aItem, flag) {
+        let aID = aItem.id;
+        if (cal.isEvent(aItem)) {
+            this.prepareStatement(this.mEditEventOfflineFlag);
+            this.mEditEventOfflineFlag.params.id = aID;
+            this.mEditEventOfflineFlag.params.offline_journal = flag || null;
+            try {
+                this.mEditEventOfflineFlag.execute();
+            } catch (e) {
+                this.logError("Error setting offline journal flag for "  + aItem.title, e);
+            } finally {
+                this.mEditEventOfflineFlag.reset();
+            }
+
+        } else if (cal.isToDo(aItem)) {
+            this.prepareStatement(this.mEditTodoOfflineFlag);
+            this.mEditTodoOfflineFlag.params.id = aID;
+            this.mEditTodoOfflineFlag.params.offline_journal = flag || null;
+            try {
+                this.mEditTodoOfflineFlag.execute();
+            } catch (e) {
+                this.logError("Error setting offline journal flag for "  + aItem.title, e);
+            } finally {
+                this.mEditEventOfflineFlag.reset();
+            }
+        }
+    },
+
+    //
+    // calIOfflineStorage interface
+    //
+    addOfflineItem: function(aItem, aListener) {
+        let newOfflineJournalFlag = cICL.OFFLINE_FLAG_CREATED_RECORD;
+        this.setOfflineJournalFlag(aItem, newOfflineJournalFlag);
+        this.notifyOperationComplete(aListener,
+                                     Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.ADD,
+                                     aItem.id,
+                                     aItem);
+    },
+
+    modifyOfflineItem: function(aItem, aListener) {
+        let this_ = this;
+        let opListener = {
+            onGetResult: function (calendar, status, itemType, detail, count, items) {
+            },
+            onOperationComplete: function(calendar, status, opType, id, oldOfflineJournalFlag ) {
+                let newOfflineJournalFlag = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
+                if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD || oldOfflineJournalFlag == cICL.OFFLINE_FLAG_DELETED_RECORD) {
+                    // Do nothing since a flag of "created" or "deleted" exists
+                }
+                else {
+                    this_.setOfflineJournalFlag(aItem, newOfflineJournalFlag);
+                }
+                this_.notifyOperationComplete(aListener,
+                                              Components.results.NS_OK,
+                                              Components.interfaces.calIOperationListener.MODIFY,
+                                              aItem.id,
+                                              aItem);
+            }
+        };
+        this.getItemOfflineFlag(aItem, opListener);
+    },
+
+    deleteOfflineItem: function(aItem, aListener) {
+        let this_ = this;
+        let opListener = {
+            onGetResult: function (calendar, status, itemType, detail, count, items) {
+
+            },
+            onOperationComplete: function(calendar, status, opType, id, oldOfflineJournalFlag) {
+                var newOfflineJournalFlag = cICL.OFFLINE_FLAG_DELETED_RECORD;
+                if (oldOfflineJournalFlag) {
+                    // Delete item if flag is c
+                    if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
+                        this_.deleteItemById(aItem.id);
+                    }
+                    else if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) {
+                        this_.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
+                    }
+                } else {
+                    this_.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
+                }
+
+                this_.notifyOperationComplete(aListener,
+                                             Components.results.NS_OK,
+                                             Components.interfaces.calIOperationListener.DELETE,
+                                             aItem.id,
+                                             aItem);
+                // notify observers
+                this_.observers.notify("onDeleteItem", [aItem]);
+            }
+        };
+        this.getItemOfflineFlag(aItem, opListener);
+    },
+
+    resetItemOfflineFlag: function(aItem, aListener) {
+        this.setOfflineJournalFlag(aItem,null);
+        this.notifyOperationComplete(aListener,
+                                     Components.results.NS_OK,
+                                     Components.interfaces.calIOperationListener.MODIFY,
+                                     aItem.id,
+                                     aItem);
+    },
+
     //
     // database handling
     //
 
     // database initialization
     // assumes mDB is valid
 
     initDB: function cSC_initDB() {
-        ASSERT(this.mDB, "Database has not been opened!", true);
+        cal.ASSERT(this.mDB, "Database has not been opened!", true);
 
         this.mSelectEvent = createStatement (
             this.mDB,
             "SELECT * FROM cal_events " +
             "WHERE id = :id AND cal_id = :cal_id " +
             " AND recurrence_id IS NULL " +
             "LIMIT 1"
             );
@@ -932,17 +1139,21 @@ calStorageCalendar.prototype = {
             "  ("+nonFloatingEventEnd+" > :range_start) OR " +
             "  ((("+floatingEventEnd+" = :range_start + :start_offset) OR " +
             "    ("+nonFloatingEventEnd+" = :range_start)) AND " +
             "   (("+floatingEventStart+" = :range_start + :start_offset) OR " +
             "    ("+nonFloatingEventStart+" = :range_start)))) " +
             " AND " +
             "  (("+floatingEventStart+" < :range_end + :end_offset) OR " +
             "   ("+nonFloatingEventStart+" < :range_end)) " +
-            " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL"
+            " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL" +
+            " AND ((:offline_journal IS NULL " +
+            " AND  (offline_journal IS NULL " +
+            "  OR   offline_journal != " + cICL.OFFLINE_FLAG_DELETED_RECORD + ")) " +
+            "  OR (offline_journal == :offline_journal))"
             );
        /**
         * WHERE (due > rangeStart AND start < rangeEnd) OR
         *       (due = rangeStart AND start = rangeStart) OR
         *       (due IS NULL AND ((start >= rangeStart AND start < rangeEnd) OR
         *                         (start IS NULL AND
         *                          (completed > rangeStart OR completed IS NULL))) OR
         *       (start IS NULL AND due >= rangeStart AND due < rangeEnd)
@@ -971,17 +1182,21 @@ calStorageCalendar.prototype = {
             "  ((("+floatingTodoEntry+" >= :range_start + :start_offset) OR " +
             "    ("+nonFloatingTodoEntry+" >= :range_start)) AND " +
             "    (("+floatingTodoEntry+" < :range_end + :end_offset) OR " +
             "     ("+nonFloatingTodoEntry+" < :range_end)))) OR " +
             " ((todo_entry IS NULL) AND " +
             "  ((("+floatingCompleted+" > :range_start + :start_offset) OR " +
             "    ("+nonFloatingCompleted+" > :range_start)) OR " +
             "   (todo_completed IS NULL)))) " +
-            " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL"
+            " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL " +
+            " AND ((:offline_journal IS NULL" +
+            " AND  (offline_journal IS NULL" +
+            "  OR   offline_journal != " + cICL.OFFLINE_FLAG_DELETED_RECORD + ")) " +
+            "  OR (offline_journal == :offline_journal))"
             );
 
         this.mSelectEventsWithRecurrence = createStatement(
             this.mDB,
             "SELECT * FROM cal_events " +
             " WHERE flags & 16 == 16 " +
             "   AND cal_id = :cal_id AND recurrence_id is NULL"
             );
@@ -1169,16 +1384,28 @@ calStorageCalendar.prototype = {
             + " VALUES (:cal_id, :item_id, :value)");
 
         this.mInsertAlarm = createStatement(
             this.mDB,
             "INSERT INTO cal_alarms " +
             "  (cal_id, item_id, icalString, recurrence_id, recurrence_id_tz) " +
             "VALUES  (:cal_id, :item_id, :icalString, :recurrence_id, :recurrence_id_tz)  "
             );
+        //Offline Operations
+        this.mEditEventOfflineFlag = createStatement(
+            this.mDB,
+            "UPDATE cal_events SET offline_journal = :offline_journal" +
+            " WHERE id = :id AND cal_id = :cal_id"
+        );
+
+        this.mEditTodoOfflineFlag = createStatement(
+            this.mDB,
+            "UPDATE cal_todos SET offline_journal = :offline_journal" +
+            " WHERE id = :id AND cal_id = :cal_id"
+        );
 
         // delete statements
         this.mDeleteEvent = createStatement (
             this.mDB,
             "DELETE FROM cal_events WHERE id = :id AND cal_id = :cal_id"
             );
         this.mDeleteTodo = createStatement (
             this.mDB,
@@ -1297,52 +1524,57 @@ calStorageCalendar.prototype = {
         if (row.last_modified) {
             item.setProperty("LAST-MODIFIED", newDateTime(row.last_modified, "UTC"));
         }
     },
 
     cacheItem: function cSC_cacheItem(item) {
         this.mItemCache[item.id] = item;
         if (item.recurrenceInfo) {
-            if (isEvent(item)) {
+            if (cal.isEvent(item)) {
                 this.mRecEventCache[item.id] = item;
             } else {
                 this.mRecTodoCache[item.id] = item;
             }
         }
     },
 
+    mRecEventCacheOfflineFlags: {},
+    mRecTodoCacheOfflineFlags : {},
     assureRecurringItemCaches: function cSC_assureRecurringItemCaches() {
         if (this.mRecItemCacheInited) {
             return;
         }
-        // build up recurring event and todo cache, because we need that on every query:
-        // for recurring items, we need to query database-wide.. yuck
+        // build up recurring event and todo cache with its offline flags,
+        // because we need that on every query: for recurring items, we need to
+        // query database-wide.. yuck
 
         try {
             this.prepareStatement(this.mSelectEventsWithRecurrence);
             let sp = this.mSelectEventsWithRecurrence.params;
             while (this.mSelectEventsWithRecurrence.step()) {
-                var row = this.mSelectEventsWithRecurrence.row;
-                var item = this.getEventFromRow(row, {});
+                let row = this.mSelectEventsWithRecurrence.row;
+                let item = this.getEventFromRow(row, {});
                 this.mRecEventCache[item.id] = item;
+                this.mRecEventCacheOfflineFlags[item.id] = row.offline_journal || null;
             }
         } catch (e) {
             this.logError("Error selecting events with recurrence!", e);
         } finally {
             this.mSelectEventsWithRecurrence.reset();
         }
 
         try {
             this.prepareStatement(this.mSelectTodosWithRecurrence);
             sp = this.mSelectTodosWithRecurrence.params;
             while (this.mSelectTodosWithRecurrence.step()) {
                 var row = this.mSelectTodosWithRecurrence.row;
                 var item = this.getTodoFromRow(row, {});
                 this.mRecTodoCache[item.id] = item;
+                this.mRecTodoCacheOfflineFlags[item.id] = row.offline_journal || null;
             }
         } catch (e) {
             this.logError("Error selecting todos with recurrence!", e);
         } finally {
             this.mSelectTodosWithRecurrence.reset();
         }
 
         this.mRecItemCacheInited = true;
@@ -1524,27 +1756,27 @@ calStorageCalendar.prototype = {
                         ritem.date = textToDate(d);
                     } else {
                         ritem = cal.createRecurrenceRule();
 
                         ritem.type = row.recur_type;
                         if (row.count) {
                             try {
                                 ritem.count = row.count;
-                            } catch(exc) {
+                            } catch (exc) {
                             }
                         } else {
                             if (row.end_date)
                                 ritem.untilDate = newDateTime(row.end_date, "UTC");
                             else
                                 ritem.untilDate = null;
                         }
                         try {
                             ritem.interval = row.interval;
-                        } catch(exc) {
+                        } catch (exc) {
                         }
 
                         var rtypes = ["second",
                                       "minute",
                                       "hour",
                                       "day",
                                       "monthday",
                                       "yearday",
@@ -1845,22 +2077,23 @@ calStorageCalendar.prototype = {
 
         flags |= this.writeAttendees(item, olditem);
         flags |= this.writeRecurrence(item, olditem);
         flags |= this.writeProperties(item, olditem);
         flags |= this.writeAttachments(item, olditem);
         flags |= this.writeRelations(item, olditem);
         flags |= this.writeAlarms(item, olditem);
 
-        if (isEvent(item))
+        if (cal.isEvent(item)) {
             this.writeEvent(item, olditem, flags);
-        else if (isToDo(item))
+        } else if (cal.isToDo(item)) {
             this.writeTodo(item, olditem, flags);
-        else
+        } else {
             throw Components.results.NS_ERROR_UNEXPECTED;
+        }
     },
 
     writeEvent: function cSC_writeEvent(item, olditem, flags) {
         try {
             this.prepareStatement(this.mInsertEvent);
             let ip = this.mInsertEvent.params;
             this.setupItemBaseParams(item, olditem, ip);
 
@@ -2187,17 +2420,17 @@ calStorageCalendar.prototype = {
         for each (let alarm in alarms) {
             let pp = this.mInsertAlarm.params;
             try {
                 this.prepareStatement(this.mInsertAlarm);
                 this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
                 pp.item_id = item.id;
                 pp.icalString = alarm.icalString;
                 this.mInsertAlarm.execute();
-            } catch(e) {
+            } catch (e) {
                 this.logError("Error writing alarm for item " + item.title + " (" + item.id + ")", e);
             } finally {
                 this.mInsertAlarm.reset();
             }
         }
 
         return CAL_ITEM_FLAG.HAS_ALARMS;
     },
@@ -2213,16 +2446,17 @@ calStorageCalendar.prototype = {
             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);
             throw e;
         }
         this.releaseTransaction();
 
         delete this.mItemCache[aID];
@@ -2240,17 +2474,17 @@ calStorageCalendar.prototype = {
     /**
      * Releases one level of transactions for this calendar.
      *
      * @param err       (optional) If set, the transaction is set to fail when
      *                    the count reaches zero.
      */
     releaseTransaction: function cSC_releaseTransaction(err) {
         if (err) {
-            cal.ERROR("DB error: " + this.mDB.lastErrorString + "\nexc: " + err);
+            cal.ERROR("[calStorageCalendar] DB error: " + this.mDB.lastErrorString + "\nexc: " + err);
             this.mDB.rollbackTransaction();
         } else {
             this.mDB.commitTransaction();
         }
     },
 
     //
     // calISyncWriteCalendar interface
@@ -2360,17 +2594,17 @@ calStorageCalendar.prototype = {
                     logMessage += "\nLast Statement param [" + param + "]: " + this.mLastStatement.params[param];
                 }
             }
         }
 
         if (exception) {
             logMessage += "\nException: " + exception;
         }
-        cal.ERROR(logMessage + "\n" + STACK(10));
+        cal.ERROR("[calStorageCalendar] " + logMessage + "\n" + cal.STACK(10));
     }
 };
 
 /** Module Registration */
 const scriptLoadOrder = [
     "calUtils.js",
 ];
 
--- a/calendar/providers/storage/calStorageUpgrade.jsm
+++ b/calendar/providers/storage/calStorageUpgrade.jsm
@@ -99,17 +99,17 @@
  */
 
 Components.utils.import("resource:///modules/Services.jsm");
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calStorageHelpers.jsm");
 
 // The current database version. Be sure to increment this when you create a new
 // updater.
-var DB_SCHEMA_VERSION = 19;
+var DB_SCHEMA_VERSION = 20;
 
 var EXPORTED_SYMBOLS = ["DB_SCHEMA_VERSION", "getSql", "getAllSql", "getSqlTable", "upgradeDB", "backupDB"];
 
 /**
  * Gets the SQL for the given table data and table name. This can be both a real
  * table or the name of an index. Indexes must contain the idx_ prefix.
  *
  * @param tblName       The name of the table or index to retrieve sql for
@@ -1326,8 +1326,29 @@ upgrade.v19 = function upgrade_v19(db, v
         }
         setDbVersionAndCommit(db, 19);
     } catch (e) {
         throw reportErrorAndRollback(db, e);
     }
 
     return tbl;
 };
+
+/**
+ * Bug 380060 - Offline Sync feature for calendar
+ * Setting a offline_journal column in cal_events tables
+ * r=philipp, p=redDragon
+ */
+upgrade.v20 = function upgrade_v20(db,version){
+    let tbl = upgrade.v19(version<19 && db, version);
+    LOGdb(db, "Storage: Upgrading to v20");
+    beginTransaction(db);
+    try{
+        //Adding a offline_journal column
+        for each (let tblName in ["cal_events", "cal_todos"]) {
+            addColumn(tbl, tblName, ["offline_journal"], "INTEGER", db);
+        }
+        setDbVersionAndCommit(db, 20);
+    } catch (e) {
+        throw reportErrorAndRollback(db,e);
+    }
+    return tbl;
+}