home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2006 June / PCpro_2006_06.ISO / files / firefox / calendar_windows_latest.xpi / components / calStorageCalendar.js < prev    next >
Encoding:
Text File  |  2006-01-21  |  68.3 KB  |  1,953 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  *  Oracle Corporation
  19.  * Portions created by the Initial Developer are Copyright (C) 2004
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. //
  40. // calStorageCalendar.js
  41. //
  42.  
  43. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  44. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  45.  
  46. const kCalICalendar = Components.interfaces.calICalendar;
  47.  
  48. const kCalCalendarManagerContractID = "@mozilla.org/calendar/manager;1";
  49. const kCalICalendarManager = Components.interfaces.calICalendarManager;
  50.  
  51. const kCalEventContractID = "@mozilla.org/calendar/event;1";
  52. const kCalIEvent = Components.interfaces.calIEvent;
  53. var CalEvent;
  54.  
  55. const kCalTodoContractID = "@mozilla.org/calendar/todo;1";
  56. const kCalITodo = Components.interfaces.calITodo;
  57. var CalTodo;
  58.  
  59. const kCalDateTimeContractID = "@mozilla.org/calendar/datetime;1";
  60. const kCalIDateTime = Components.interfaces.calIDateTime;
  61. var CalDateTime;
  62.  
  63. const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
  64. const kCalIAttendee = Components.interfaces.calIAttendee;
  65. var CalAttendee;
  66.  
  67. const kCalRecurrenceInfoContractID = "@mozilla.org/calendar/recurrence-info;1";
  68. const kCalIRecurrenceInfo = Components.interfaces.calIRecurrenceInfo;
  69. var CalRecurrenceInfo;
  70.  
  71. const kCalRecurrenceRuleContractID = "@mozilla.org/calendar/recurrence-rule;1";
  72. const kCalIRecurrenceRule = Components.interfaces.calIRecurrenceRule;
  73. var CalRecurrenceRule;
  74.  
  75. const kCalRecurrenceDateSetContractID = "@mozilla.org/calendar/recurrence-date-set;1";
  76. const kCalIRecurrenceDateSet = Components.interfaces.calIRecurrenceDateSet;
  77. var CalRecurrenceDateSet;
  78.  
  79. const kCalRecurrenceDateContractID = "@mozilla.org/calendar/recurrence-date;1";
  80. const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
  81. var CalRecurrenceDate;
  82.  
  83. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  84. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  85. var MozStorageStatementWrapper;
  86.  
  87. if (!kMozStorageStatementWrapperIID) {
  88.     dump("*** mozStorage not available, calendar/storage provider will not function\n");
  89. }
  90.  
  91. const CAL_ITEM_TYPE_EVENT = 0;
  92. const CAL_ITEM_TYPE_TODO = 1;
  93.  
  94. // bitmasks
  95. const CAL_ITEM_FLAG_PRIVATE = 1;
  96. const CAL_ITEM_FLAG_HAS_ATTENDEES = 2;
  97. const CAL_ITEM_FLAG_HAS_PROPERTIES = 4;
  98. const CAL_ITEM_FLAG_EVENT_ALLDAY = 8;
  99. const CAL_ITEM_FLAG_HAS_RECURRENCE = 16;
  100. const CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32;
  101.  
  102. const USECS_PER_SECOND = 1000000;
  103.  
  104. function initCalStorageCalendarComponent() {
  105.     CalEvent = new Components.Constructor(kCalEventContractID, kCalIEvent);
  106.     CalTodo = new Components.Constructor(kCalTodoContractID, kCalITodo);
  107.     CalDateTime = new Components.Constructor(kCalDateTimeContractID, kCalIDateTime);
  108.     CalAttendee = new Components.Constructor(kCalAttendeeContractID, kCalIAttendee);
  109.     CalRecurrenceInfo = new Components.Constructor(kCalRecurrenceInfoContractID, kCalIRecurrenceInfo);
  110.     CalRecurrenceRule = new Components.Constructor(kCalRecurrenceRuleContractID, kCalIRecurrenceRule);
  111.     CalRecurrenceDateSet = new Components.Constructor(kCalRecurrenceDateSetContractID, kCalIRecurrenceDateSet);
  112.     CalRecurrenceDate = new Components.Constructor(kCalRecurrenceDateContractID, kCalIRecurrenceDate);
  113.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  114. }
  115.  
  116. //
  117. // Storage helpers
  118. //
  119.  
  120. function createStatement (dbconn, sql) {
  121.     try {
  122.         var stmt = dbconn.createStatement(sql);
  123.         var wrapper = MozStorageStatementWrapper();
  124.         wrapper.initialize(stmt);
  125.         return wrapper;
  126.     } catch (e) {
  127.         Components.utils.reportError(
  128.             "mozStorage exception: createStatement failed, statement: '" + 
  129.             sql + "', error: '" + dbconn.lastErrorString + "'");
  130.     }
  131.  
  132.     return null;
  133. }
  134.  
  135. function textToDate(d) {
  136.     var dval;
  137.     var tz = "UTC";
  138.  
  139.     if (d[0] == 'Z') {
  140.         var strs = d.substr(2).split(":");
  141.         dval = parseInt(strs[0]);
  142.         tz = strs[1].replace(/%:/g, ":").replace(/%%/g, "%");
  143.     } else {
  144.         dval = parseInt(d.substr(2));
  145.     }
  146.  
  147.     var date;
  148.     if (d[0] == 'U' || d[0] == 'Z') {
  149.         date = newDateTime(dval, tz);
  150.     } else if (d[0] == 'L') {
  151.         // is local time
  152.         date = newDateTime(dval, "floating");
  153.     }
  154.  
  155.     if (d[1] == 'D')
  156.         date.isDate = true;
  157.     return date;
  158. }
  159.  
  160. function dateToText(d) {
  161.     var datestr;
  162.     var tz = null;
  163.     if (d.timezone != "floating") {
  164.         if (d.timezone == "UTC") {
  165.             datestr = "U";
  166.         } else {
  167.             datestr = "Z";
  168.             tz = d.timezone;
  169.         }
  170.     } else {
  171.         datestr = "L";
  172.     }
  173.  
  174.     if (d.isDate) {
  175.         datestr += "D";
  176.     } else {
  177.         datestr += "T";
  178.     }
  179.  
  180.     datestr += d.nativeTime;
  181.  
  182.     if (tz) {
  183.         // replace '%' with '%%', then replace ':' with '%:'
  184.         tz = tz.replace(/%/g, "%%");
  185.         tz = tz.replace(/:/g, "%:");
  186.         datestr += ":" + tz;
  187.     }
  188.     return datestr;
  189. }
  190.  
  191. // 
  192. // other helpers
  193. //
  194.  
  195. function newDateTime(aNativeTime, aTimezone) {
  196.     var t = new CalDateTime();
  197.     t.nativeTime = aNativeTime;
  198.     if (aTimezone && aTimezone != "floating") {
  199.         t = t.getInTimezone(aTimezone);
  200.     } else {
  201.         t.timezone = "floating";
  202.     }
  203.  
  204.     return t;
  205. }
  206.  
  207. //
  208. // calStorageCalendar
  209. //
  210.  
  211. var activeCalendarManager = null;
  212. function getCalendarManager()
  213. {
  214.     if (!activeCalendarManager) {
  215.         activeCalendarManager = 
  216.             Components.classes[kCalCalendarManagerContractID].getService(kCalICalendarManager);
  217.     }
  218.     return activeCalendarManager;
  219. }
  220.  
  221. function calStorageCalendar() {
  222.     this.wrappedJSObject = this;
  223.     this.mObservers = new Array();
  224.     this.mItemCache = new Array();
  225. }
  226.  
  227. calStorageCalendar.prototype = {
  228.     //
  229.     // private members
  230.     //
  231.     mDB: null,
  232.     mDBTwo: null,
  233.     mCalId: 0,
  234.  
  235.     //
  236.     // nsISupports interface
  237.     // 
  238.     QueryInterface: function (aIID) {
  239.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  240.             !aIID.equals(Components.interfaces.calICalendar))
  241.         {
  242.             throw Components.results.NS_ERROR_NO_INTERFACE;
  243.         }
  244.  
  245.         return this;
  246.     },
  247.  
  248.     //
  249.     // calICalendar interface
  250.     //
  251.  
  252.     // attribute AUTF8String name;
  253.     get name() {
  254.         return getCalendarManager().getCalendarPref(this, "NAME");
  255.     },
  256.     set name(name) {
  257.         getCalendarManager().setCalendarPref(this, "NAME", name);
  258.     },
  259.     // readonly attribute AUTF8String type;
  260.     get type() { return "storage"; },
  261.  
  262.     mReadOnly: false,
  263.  
  264.     get readOnly() { 
  265.         return this.mReadOnly;
  266.     },
  267.     set readOnly(bool) {
  268.         this.mReadOnly = bool;
  269.     },
  270.  
  271.     mURI: null,
  272.     // attribute nsIURI uri;
  273.     get uri() { return this.mURI; },
  274.     set uri(aURI) {
  275.         // we can only load once
  276.         if (this.mURI)
  277.             throw Components.results.NS_ERROR_FAILURE;
  278.  
  279.         var id = 0;
  280.  
  281.         // check if there's a ?id=
  282.         var path = aURI.path;
  283.         var pos = path.indexOf("?id=");
  284.  
  285.         if (pos != -1) {
  286.             id = parseInt(path.substr(pos+4));
  287.             path = path.substr(0, pos);
  288.         }
  289.  
  290.         var dbService;
  291.         if (aURI.scheme == "file") {
  292.             var fileURL = aURI.QueryInterface(Components.interfaces.nsIFileURL);
  293.             if (!fileURL)
  294.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  295.  
  296.             // open the database
  297.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  298.             this.mDB = dbService.openDatabase (fileURL.file);
  299.             this.mDBTwo = dbService.openDatabase (fileURL.file);
  300.         } else if (aURI.scheme == "moz-profile-calendar") {
  301.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  302.         if ( "getProfileStorage" in dbService ) {
  303.           // 1.8 branch
  304.           this.mDB = dbService.getProfileStorage("profile");
  305.           this.mDBTwo = dbService.getProfileStorage("profile");
  306.         } else {
  307.           // trunk 
  308.           this.mDB = dbService.openSpecialDatabase("profile");
  309.           this.mDBTwo = dbService.openSpecialDatabase("profile");
  310.         }
  311.     }
  312.  
  313.         this.initDB();
  314.  
  315.         this.mCalId = id;
  316.         this.mURI = aURI;
  317.     },
  318.  
  319.     refresh: function() {
  320.         // no-op
  321.     },
  322.  
  323.     // attribute boolean suppressAlarms;
  324.     get suppressAlarms() { return false; },
  325.     set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
  326.  
  327.     // void addObserver( in calIObserver observer );
  328.     addObserver: function (aObserver, aItemFilter) {
  329.         for each (obs in this.mObservers) {
  330.             if (obs == aObserver)
  331.                 return;
  332.         }
  333.  
  334.         this.mObservers.push(aObserver);
  335.     },
  336.  
  337.     // void removeObserver( in calIObserver observer );
  338.     removeObserver: function (aObserver) {
  339.         var newObservers = Array();
  340.         for each (obs in this.mObservers) {
  341.             if (obs != aObserver)
  342.                 newObservers.push(obs);
  343.         }
  344.         this.mObservers = newObservers;
  345.     },
  346.  
  347.     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
  348.     addItem: function (aItem, aListener) {
  349.         var newItem = aItem.clone();
  350.         return this.adoptItem(newItem, aListener);
  351.     },
  352.  
  353.     // void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
  354.     adoptItem: function (aItem, aListener) {
  355.         if (this.readOnly) 
  356.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  357.         // Ensure that we're looking at the base item
  358.         // if we were given an occurrence.  Later we can
  359.         // optimize this.
  360.         if (aItem.parentItem != aItem) {
  361.             aItem.parentItem.recurrenceInfo.modifyException(aItem);
  362.         }
  363.         aItem = aItem.parentItem;
  364.  
  365.         if (aItem.id == null) {
  366.             // is this an error?  Or should we generate an IID?
  367.             aItem.id = "uuid:" + (new Date()).getTime();
  368.         } else {
  369.             var olditem = this.getItemById(aItem.id);
  370.             if (olditem) {
  371.                 if (aListener)
  372.                     aListener.onOperationComplete (this,
  373.                                                    Components.results.NS_ERROR_FAILURE,
  374.                                                    aListener.ADD,
  375.                                                    aItem.id,
  376.                                                    "ID already exists for addItem");
  377.                 return;
  378.             }
  379.         }
  380.  
  381.         aItem.calendar = this;
  382.         aItem.generation = 1;
  383.         aItem.makeImmutable();
  384.  
  385.         this.flushItem (aItem, null);
  386.  
  387.         // notify the listener
  388.         if (aListener)
  389.             aListener.onOperationComplete (this,
  390.                                            Components.results.NS_OK,
  391.                                            aListener.ADD,
  392.                                            aItem.id,
  393.                                            aItem);
  394.  
  395.         // notify observers
  396.         this.observeAddItem(aItem);
  397.     },
  398.  
  399.     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
  400.     modifyItem: function (aNewItem, aOldItem, aListener) {
  401.         if (this.readOnly) 
  402.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  403.         function reportError(errId, errStr) {
  404.             if (aListener)
  405.                 aListener.onOperationComplete (this,
  406.                                                errId ? errId : Components.results.NS_ERROR_FAILURE,
  407.                                                aListener.MODIFY,
  408.                                                aNewItem.id,
  409.                                                errStr);
  410.         }
  411.  
  412.         if (aNewItem.id == null) {
  413.             // this is definitely an error
  414.             reportError (null, "ID for modifyItem item is null");
  415.             return;
  416.         }
  417.  
  418.         // Ensure that we're looking at the base item
  419.         // if we were given an occurrence.  Later we can
  420.         // optimize this.
  421.         if (aNewItem.parentItem != aNewItem) {
  422.             aNewItem.parentItem.recurrenceInfo.modifyException(aNewItem);
  423.         }
  424.  
  425.         aNewItem = aNewItem.parentItem;
  426.  
  427.         // get the old item
  428.         var olditem = this.getItemById(aOldItem.id);
  429.         if (!olditem) {
  430.             // no old item found?  should be using addItem, then.
  431.             if (aListener)
  432.                 aListener.onOperationComplete (this,
  433.                                                Components.results.NS_ERROR_FAILURE,
  434.                                                aListener.MODIFY,
  435.                                                aNewItem.id,
  436.                                                "ID does not already exist for modifyItem");
  437.             return;
  438.         }
  439.  
  440.         if (aOldItem.generation != aNewItem.generation) {
  441.             if (aListener)
  442.                 aListener.onOperationComplete (this,
  443.                                                Components.results.NS_ERROR_FAILURE,
  444.                                                aListener.MODIFY,
  445.                                                aNewItem.id,
  446.                                                "generation too old for for modifyItem");
  447.             return;
  448.         }
  449.  
  450.         var modifiedItem = aNewItem.clone();
  451.         modifiedItem.generation += 1;
  452.         modifiedItem.makeImmutable();
  453.  
  454.         this.flushItem (modifiedItem, aOldItem);
  455.  
  456.         if (aListener)
  457.             aListener.onOperationComplete (this,
  458.                                            Components.results.NS_OK,
  459.                                            aListener.MODIFY,
  460.                                            modifiedItem.id,
  461.                                            modifiedItem);
  462.  
  463.         // notify observers
  464.         this.observeModifyItem(modifiedItem, aOldItem);
  465.     },
  466.  
  467.     // void deleteItem( in string id, in calIOperationListener aListener );
  468.     deleteItem: function (aItem, aListener) {
  469.         if (this.readOnly) 
  470.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  471.         if (aItem.parentItem != aItem) {
  472.             aItem.parentItem.recurrenceInfo.removeExceptionFor(aItem.recurrenceId);
  473.             return;
  474.         }
  475.  
  476.         if (aItem.id == null) {
  477.             if (aListener)
  478.                 aListener.onOperationComplete (this,
  479.                                                Components.results.NS_ERROR_FAILURE,
  480.                                                aListener.DELETE,
  481.                                                null,
  482.                                                "ID is null for deleteItem");
  483.             return;
  484.         }
  485.  
  486.         this.deleteItemById(aItem.id);
  487.  
  488.         if (aListener)
  489.             aListener.onOperationComplete (this,
  490.                                            Components.results.NS_OK,
  491.                                            aListener.DELETE,
  492.                                            aItem.id,
  493.                                            null);
  494.  
  495.         // notify observers 
  496.         this.observeDeleteItem(aItem);
  497.     },
  498.  
  499.     // void getItem( in string id, in calIOperationListener aListener );
  500.     getItem: function (aId, aListener) {
  501.         if (!aListener)
  502.             return;
  503.  
  504.         var item = this.getItemById (aId);
  505.         if (!item) {
  506.             aListener.onOperationComplete (this,
  507.                                            Components.results.NS_ERROR_FAILURE,
  508.                                            aListener.GET,
  509.                                            aId,
  510.                                            "ID doesn't exist for getItem");
  511.         }
  512.  
  513.         var item_iid = null;
  514.         if (item instanceof kCalIEvent)
  515.             item_iid = kCalIEvent;
  516.         else if (item instanceof kCalITodo)
  517.             item_iid = kCalITodo;
  518.         else {
  519.             aListener.onOperationComplete (this,
  520.                                            Components.results.NS_ERROR_FAILURE,
  521.                                            aListener.GET,
  522.                                            aId,
  523.                                            "Can't deduce item type based on QI");
  524.             return;
  525.         }
  526.  
  527.         aListener.onGetResult (this,
  528.                                Components.results.NS_OK,
  529.                                item_iid, null,
  530.                                1, [item]);
  531.  
  532.         aListener.onOperationComplete (this,
  533.                                        Components.results.NS_OK,
  534.                                        aListener.GET,
  535.                                        aId,
  536.                                        null);
  537.     },
  538.  
  539.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
  540.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  541.     //                in calIOperationListener aListener );
  542.     getItems: function (aItemFilter, aCount,
  543.                         aRangeStart, aRangeEnd, aListener)
  544.     {
  545.         //var profStartTime = Date.now();
  546.         if (!aListener)
  547.             return;
  548.  
  549.         var self = this;
  550.  
  551.         var itemsFound = Array();
  552.         var startTime = -0x7fffffffffffffff;
  553.         // endTime needs to be the max value a PRTime can be
  554.         var endTime = 0x7fffffffffffffff;
  555.         var count = 0;
  556.         if (aRangeStart)
  557.             startTime = aRangeStart.nativeTime;
  558.         if (aRangeEnd)
  559.             endTime = aRangeEnd.nativeTime;
  560.  
  561.         var wantEvents = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_EVENT) != 0);
  562.         var wantTodos = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_TODO) != 0);
  563.         var asOccurrences = ((aItemFilter & kCalICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0);
  564.         if (!wantEvents && !wantTodos) {
  565.             // nothing to do
  566.             aListener.onOperationComplete (this,
  567.                                            Components.results.NS_OK,
  568.                                            aListener.GET,
  569.                                            null,
  570.                                            null);
  571.             return;
  572.         }
  573.  
  574.         var wantCompletedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_YES) != 0);
  575.         var wantNotCompletedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_NO) != 0);
  576.         
  577.         // sending items to the listener 1 at a time sucks. instead,
  578.         // queue them up.
  579.         // if we ever have more than maxQueueSize items outstanding,
  580.         // call the listener.  Calling with null theItems forces
  581.         // a send and a queue clear.
  582.         var maxQueueSize = 10;
  583.         var queuedItems = [ ];
  584.         var queuedItemsIID;
  585.         function queueItems(theItems, theIID) {
  586.             // if we're about to start sending a different IID,
  587.             // flush the queue
  588.             if (theIID && queuedItemsIID != theIID) {
  589.                 if (queuedItemsIID)
  590.                     queueItems(null);
  591.                 queuedItemsIID = theIID;
  592.             }
  593.  
  594.             if (theItems)
  595.                 queuedItems = queuedItems.concat(theItems);
  596.  
  597.             if (queuedItems.length != 0 && (!theItems || queuedItems.length > maxQueueSize)) {
  598.                 //var listenerStart = Date.now();
  599.                 aListener.onGetResult(self,
  600.                                       Components.results.NS_OK,
  601.                                       queuedItemsIID, null,
  602.                                       queuedItems.length, queuedItems);
  603.                 //var listenerEnd = Date.now();
  604.                 //dump ("++++ listener callback took: " + (listenerEnd - listenerStart) + " ms\n");
  605.  
  606.                 queuedItems = [ ];
  607.             }
  608.         }
  609.  
  610.         // helper function to handle converting a row to an item,
  611.         // expanding occurrences, and queue the items for the listener
  612.         function handleResultItem(item, flags, theIID) {
  613.             self.getAdditionalDataForItem(item, flags);
  614.             item.makeImmutable();
  615.  
  616.             var expandedItems;
  617.  
  618.             if (asOccurrences && item.recurrenceInfo) {
  619.                 expandedItems = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {});
  620.             } else {
  621.                 expandedItems = [ item ];
  622.             }
  623.  
  624.             queueItems (expandedItems, theIID);
  625.  
  626.             return expandedItems.length;
  627.         }
  628.  
  629.         // check the count and send end if count is exceeded
  630.         function checkCount() {
  631.             if (aCount && count >= aCount) {
  632.                 // flush queue
  633.                 queueItems(null);
  634.  
  635.                 // send operation complete
  636.                 aListener.onOperationComplete (self,
  637.                                                Components.results.NS_OK,
  638.                                                aListener.GET,
  639.                                                null,
  640.                                                null);
  641.  
  642.                 // tell caller we're done
  643.                 return true;
  644.             }
  645.  
  646.             return false;
  647.         }
  648.  
  649.         // First fetch all the events
  650.         if (wantEvents) {
  651.             // this will contain a lookup table of item ids that we've already dealt with,
  652.             // if we have recurrence to care about.
  653.             var handledRecurringEvents = { };
  654.             var sp;             // stmt params
  655.  
  656.             var resultItems = [];
  657.  
  658.             // first get non-recurring events and recurring events that happen
  659.             // to fall within the range
  660.             sp = this.mSelectEventsByRange.params;
  661.             sp.cal_id = this.mCalId;
  662.             sp.range_start = startTime;
  663.             sp.range_end = endTime;
  664.             sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  665.             sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
  666.  
  667.             while (this.mSelectEventsByRange.step()) {
  668.                 var row = this.mSelectEventsByRange.row;
  669.                 var flags = {};
  670.                 var item = this.getEventFromRow(row, flags);
  671.                 flags = flags.value;
  672.  
  673.                 resultItems.push({item: item, flags: flags});
  674.  
  675.                 if (asOccurrences && flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
  676.                     handledRecurringEvents[row.id] = true;
  677.             }
  678.             this.mSelectEventsByRange.reset();
  679.  
  680.             // then, if we want occurrences, we need to query database-wide.. yuck
  681.             if (asOccurrences) {
  682.                 sp = this.mSelectEventsWithRecurrence.params;
  683.                 sp.cal_id = this.mCalId;
  684.                 while (this.mSelectEventsWithRecurrence.step()) {
  685.                     var row = this.mSelectEventsWithRecurrence.row;
  686.                     // did we already deal with this event id?
  687.                     if (handledRecurringEvents[row.id] == true)
  688.                         continue;
  689.  
  690.                     var flags = {};
  691.                     var item = this.getEventFromRow(row, flags);
  692.                     flags = flags.value;
  693.  
  694.                     resultItems.push({item: item, flags: flags});
  695.                 }
  696.                 this.mSelectEventsWithRecurrence.reset();
  697.             }
  698.  
  699.             // process the events
  700.             for each (var evitem in resultItems) {
  701.                 count += handleResultItem(evitem.item, evitem.flags, kCalIEvent);
  702.                 if (checkCount())
  703.                     return;
  704.             }
  705.         }
  706.  
  707.  
  708.         // if todos are wanted, do them next
  709.         if (wantTodos) {
  710.             // this will contain a lookup table of item ids that we've already dealt with,
  711.             // if we have recurrence to care about.
  712.             var handledRecurringTodos = { };
  713.             var sp;             // stmt params
  714.  
  715.             var resultItems = [];
  716.  
  717.             // first get non-recurring todos and recurring todos that happen
  718.             // to fall within the range
  719.             sp = this.mSelectTodosByRange.params;
  720.             sp.cal_id = this.mCalId;
  721.             sp.range_start = startTime;
  722.             sp.range_end = endTime;
  723.             sp.offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  724.  
  725.             while (this.mSelectTodosByRange.step()) {
  726.                 var row = this.mSelectTodosByRange.row;
  727.                 var flags = {};
  728.                 var item = this.getTodoFromRow(row, flags);
  729.                 flags = flags.value;
  730.  
  731.                 var itemIsCompleted = false;
  732.                 if (item.percentComplete == 100 ||
  733.                     item.completedDate != null ||
  734.                     item.ical_status == kCalITodo.CAL_TODO_STATUS_COMPLETED)
  735.                     itemIsCompleted = true;
  736.  
  737.                 if (!itemIsCompleted && !wantNotCompletedItems)
  738.                     continue;
  739.                 if (itemIsCompleted && !wantCompletedItems)
  740.                     continue;
  741.  
  742.                 var completed = 
  743.                 resultItems.push({item: item, flags: flags});
  744.                 if (asOccurrences && row.flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
  745.                     handledRecurringTodos[row.id] = true;
  746.             }
  747.             this.mSelectTodosByRange.reset();
  748.  
  749.             // then, if we want occurrences, we need to query database-wide.. yuck
  750.             if (asOccurrences) {
  751.                 sp = this.mSelectTodosWithRecurrence.params;
  752.                 sp.cal_id = this.mCalId;
  753.                 while (this.mSelectTodosWithRecurrence.step()) {
  754.                     var row = this.mSelectTodosByRange.row;
  755.                     // did we already deal with this todo id?
  756.                     if (handledRecurringTodos[row.id] == true)
  757.                         continue;
  758.  
  759.                     var flags = {};
  760.                     var item = this.getTodoFromRow(row, flags);
  761.                     flags = flags.value;
  762.  
  763.                     var itemIsCompleted = false;
  764.                     if (item.todo_complete == 100 ||
  765.                         item.todo_completed != null ||
  766.                         item.ical_status == kCalITodo.CAL_TODO_STATUS_COMPLETED)
  767.                         itemIsCompleted = true;
  768.  
  769.                     if (!itemIsCompleted && !wantNotCompletedItems)
  770.                         continue;
  771.                     if (itemIsCompleted && !wantCompletedItems)
  772.                         continue;
  773.  
  774.                     resultItems.push({item: item, flags: flags});
  775.                 }
  776.                 this.mSelectTodosWithRecurrence.reset();
  777.             }
  778.  
  779.             // process the todos
  780.             for each (var todoitem in resultItems) {
  781.                 count += handleResultItem(todoitem.item, todoitem.flags, kCalITodo);
  782.                 if (checkCount())
  783.                     return;
  784.             }
  785.         }
  786.  
  787.         // flush the queue
  788.         queueItems(null);
  789.  
  790.         // and finish
  791.         aListener.onOperationComplete (this,
  792.                                        Components.results.NS_OK,
  793.                                        aListener.GET,
  794.                                        null,
  795.                                        null);
  796.  
  797.         //var profEndTime = Date.now();
  798.         //dump ("++++ getItems took: " + (profEndTime - profStartTime) + " ms\n");
  799.     },
  800.  
  801.     startBatch: function ()
  802.     {
  803.         this.observeBatchChange(true);
  804.     },
  805.     endBatch: function ()
  806.     {
  807.         this.observeBatchChange(false);
  808.     },
  809.  
  810.     //
  811.     // Helper functions
  812.     //
  813.     observeLoad: function () {
  814.         for each (obs in this.mObservers)
  815.             obs.onLoad ();
  816.     },
  817.  
  818.     observeBatchChange: function (aNewBatchMode) {
  819.         for each (obs in this.mObservers) {
  820.             if (aNewBatchMode)
  821.                 obs.onStartBatch ();
  822.             else
  823.                 obs.onEndBatch ();
  824.         }
  825.     },
  826.  
  827.     observeAddItem: function (aItem) {
  828.         for each (obs in this.mObservers)
  829.             obs.onAddItem (aItem);
  830.     },
  831.  
  832.     observeModifyItem: function (aNewItem, aOldItem) {
  833.         for each (obs in this.mObservers)
  834.             obs.onModifyItem (aNewItem, aOldItem);
  835.     },
  836.  
  837.     observeDeleteItem: function (aDeletedItem) {
  838.         for each (obs in this.mObservers)
  839.             obs.onDeleteItem (aDeletedItem);
  840.     },
  841.  
  842.     //
  843.     // database handling
  844.     //
  845.  
  846.     // initialize the database schema.
  847.     // needs to do some version checking
  848.     initDBSchema: function () {
  849.         for (table in sqlTables) {
  850.             dump (table + "\n");
  851.             try {
  852.                 this.mDB.executeSimpleSQL("DROP TABLE " + table);
  853.             } catch (e) { }
  854.             this.mDB.createTable(table, sqlTables[table]);
  855.         }
  856.  
  857.         this.mDB.executeSimpleSQL("INSERT INTO cal_calendar_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
  858.     },
  859.  
  860.     // check db version
  861.     DB_SCHEMA_VERSION: 4,
  862.     versionCheck: function () {
  863.         var version = -1;
  864.         var selectSchemaVersion;
  865.  
  866.         try {
  867.             selectSchemaVersion = createStatement (this.mDB, "SELECT version FROM cal_calendar_schema_version LIMIT 1");
  868.             if (selectSchemaVersion.step()) {
  869.                 version = selectSchemaVersion.row.version;
  870.             }
  871.         } catch (e) {
  872.             // either the cal_calendar_schema_version table is not
  873.             // found, or something else happened
  874.             version = -1;
  875.         }
  876.         if (selectSchemaVersion)
  877.             selectSchemaVersion.reset();
  878.  
  879.         return version;
  880.     },
  881.  
  882.     upgradeDB: function (oldVersion) {
  883.         // some common helpers
  884.         function addColumn(db, tableName, colName, colType) {
  885.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  886.         }
  887.  
  888.         if (oldVersion == 2 && this.DB_SCHEMA_VERSION >= 3) {
  889.             dump ("**** Upgrading schema from 2 -> 3\n");
  890.  
  891.             this.mDB.beginTransaction();
  892.             try {
  893.                 // the change between 2 and 3 includes the splitting of cal_items into
  894.                 // cal_events and cal_todos, and the addition of columns for
  895.                 // event_start_tz, event_end_tz, todo_entry_tz, todo_due_tz.
  896.                 // These need to default to "UTC" if their corresponding time is
  897.                 // given, since that's what the default was for v2 calendars
  898.  
  899.                 // create the two new tables
  900.                 try { this.mDB.executeSimpleSQL("DROP TABLE cal_events; DROP TABLE cal_todos;"); } catch (e) { }
  901.                 this.mDB.createTable("cal_events", sqlTables["cal_events"]);
  902.                 this.mDB.createTable("cal_todos", sqlTables["cal_todos"]);
  903.  
  904.                 // copy stuff over
  905.                 var eventCols = ["cal_id", "id", "time_created", "last_modified", "title",
  906.                                  "priority", "privacy", "ical_status", "flags",
  907.                                  "event_start", "event_end", "event_stamp"];
  908.                 var todoCols = ["cal_id", "id", "time_created", "last_modified", "title",
  909.                                 "priority", "privacy", "ical_status", "flags",
  910.                                 "todo_entry", "todo_due", "todo_completed", "todo_complete"];
  911.  
  912.                 this.mDB.executeSimpleSQL("INSERT INTO cal_events(" + eventCols.join(",") + ") " +
  913.                                           "     SELECT " + eventCols.join(",") +
  914.                                           "       FROM cal_items WHERE item_type = 0");
  915.                 this.mDB.executeSimpleSQL("INSERT INTO cal_todos(" + todoCols.join(",") + ") " +
  916.                                           "     SELECT " + todoCols.join(",") +
  917.                                           "       FROM cal_items WHERE item_type = 1");
  918.  
  919.                 // now fix up the new _tz columns
  920.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_start_tz = 'UTC' WHERE event_start IS NOT NULL");
  921.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_end_tz = 'UTC' WHERE event_end IS NOT NULL");
  922.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_entry_tz = 'UTC' WHERE todo_entry IS NOT NULL");
  923.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_due_tz = 'UTC' WHERE todo_due IS NOT NULL");
  924.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_completed_tz = 'UTC' WHERE todo_completed IS NOT NULL");
  925.  
  926.                 // finally update the version
  927.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (3);");
  928.  
  929.                 this.mDB.commitTransaction();
  930.  
  931.                 oldVersion = 3;
  932.             } catch (e) {
  933.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  934.                 Components.utils.reportError("Upgrade failed! DB Error: " + 
  935.                                              this.mDB.lastErrorString);
  936.                 this.mDB.rollbackTransaction();
  937.                 throw e;
  938.             }
  939.         }
  940.  
  941.         if (oldVersion == 3 && this.DB_SCHEMA_VERSION >= 4) {
  942.             dump ("**** Upgrading schema from 3 -> 4\n");
  943.  
  944.             this.mDB.beginTransaction();
  945.             try {
  946.                 // the change between 3 and 4 is the addition of
  947.                 // recurrence_id and recurrence_id_tz columns to
  948.                 // cal_events, cal_todos, cal_attendees, and cal_properties
  949.                 addColumn(this.mDB, "cal_events", "recurrence_id", "INTEGER");
  950.                 addColumn(this.mDB, "cal_events", "recurrence_id_tz", "VARCHAR");
  951.  
  952.                 addColumn(this.mDB, "cal_todos", "recurrence_id", "INTEGER");
  953.                 addColumn(this.mDB, "cal_todos", "recurrence_id_tz", "VARCHAR");
  954.  
  955.                 addColumn(this.mDB, "cal_attendees", "recurrence_id", "INTEGER");
  956.                 addColumn(this.mDB, "cal_attendees", "recurrence_id_tz", "VARCHAR");
  957.  
  958.                 addColumn(this.mDB, "cal_properties", "recurrence_id", "INTEGER");
  959.                 addColumn(this.mDB, "cal_properties", "recurrence_id_tz", "VARCHAR");
  960.  
  961.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (4);");
  962.                 this.mDB.commitTransaction();
  963.  
  964.                 oldVersion = 4;
  965.             } catch (e) {
  966.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  967.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  968.                                              this.mDB.lastErrorString);
  969.                 this.mDB.rollbackTransaction();
  970.                 throw e;
  971.             }
  972.         }
  973.  
  974.         if (oldVersion != 4) {
  975.             dump ("#######!!!!! calStorageCalendar Schema Update failed -- db version: " + oldVersion + " this version: " + this.DB_SCHEMA_VERSION + "\n");
  976.             throw Components.results.NS_ERROR_FAILURE;
  977.         }
  978.     },
  979.  
  980.     // database initialization
  981.     // assumes mDB is valid
  982.  
  983.     initDB: function () {
  984.         var version = this.versionCheck();
  985.         dump ("*** Calendar schema version is: " + version + "\n");
  986.         if (version == -1) {
  987.             this.initDBSchema();
  988.         } else if (version != this.DB_SCHEMA_VERSION) {
  989.             this.upgradeDB(version);
  990.         }
  991.  
  992.         this.mSelectEvent = createStatement (
  993.             this.mDB,
  994.             "SELECT * FROM cal_events " +
  995.             "WHERE id = :id AND recurrence_id IS NULL " +
  996.             "LIMIT 1"
  997.             );
  998.  
  999.         this.mSelectTodo = createStatement (
  1000.             this.mDB,
  1001.             "SELECT * FROM cal_todos " +
  1002.             "WHERE id = :id AND recurrence_id IS NULL " +
  1003.             "LIMIT 1"
  1004.             );
  1005.  
  1006.         // The more readable version of the next where-clause is:
  1007.         //   WHERE event_end >= :range_start AND event_start < :event_end
  1008.         // but that doesn't work with floating start or end times. The logic
  1009.         // is the same though.
  1010.         // For readability, a few helpers:
  1011.         var floatingEventStart = "event_start_tz = 'floating' AND event_start - :start_offset"
  1012.         var nonFloatingEventStart = "event_start_tz != 'floating' AND event_start"
  1013.         var floatingEventEnd = "event_end_tz = 'floating' AND event_end - :end_offset"
  1014.         var nonFloatingEventEnd = "event_end_tz != 'floating' AND event_end"
  1015.         // The query needs to take both floating and non floating into account
  1016.         this.mSelectEventsByRange = createStatement(
  1017.             this.mDB,
  1018.             "SELECT * FROM cal_events " +
  1019.             "WHERE " +
  1020.             " (("+floatingEventEnd+" >= :range_start) OR " +
  1021.             "  ("+nonFloatingEventEnd+" >= :range_start)) AND " +
  1022.             " (("+floatingEventStart+" < :range_end) OR " +
  1023.             "  ("+nonFloatingEventStart+" < :range_end)) " +
  1024.             "  AND cal_id = :cal_id AND recurrence_id IS NULL"
  1025.             );
  1026.  
  1027.         var floatingTodoEntry = "todo_entry_tz = 'floating' AND todo_entry - :offset"
  1028.         var nonFloatingTodoEntry = "todo_entry_tz != 'floating' AND todo_entry"
  1029.         this.mSelectTodosByRange = createStatement(
  1030.             this.mDB,
  1031.             "SELECT * FROM cal_todos " +
  1032.             "WHERE " +
  1033.             " (((("+floatingTodoEntry+" >= :range_start) OR " +
  1034.             "    ("+nonFloatingTodoEntry+" >= :range_start)) AND " +
  1035.             "   (("+floatingTodoEntry+" < :range_end) OR " +
  1036.             "    ("+nonFloatingTodoEntry+" < :range_end))) " +
  1037.             "  OR (todo_entry IS NULL)) " +
  1038.             " AND cal_id = :cal_id AND recurrence_id IS NULL"
  1039.             );
  1040.  
  1041.         this.mSelectEventsWithRecurrence = createStatement(
  1042.             this.mDB,
  1043.             "SELECT * FROM cal_events " +
  1044.             " WHERE flags & 16 == 16 " +
  1045.             "   AND cal_id = :cal_id AND recurrence_id is NULL"
  1046.             );
  1047.  
  1048.         this.mSelectTodosWithRecurrence = createStatement(
  1049.             this.mDB,
  1050.             "SELECT * FROM cal_todos " +
  1051.             " WHERE flags & 16 == 16 " +
  1052.             "   AND cal_id = :cal_id AND recurrence_id IS NULL"
  1053.             );
  1054.  
  1055.         this.mSelectEventExceptions = createStatement (
  1056.             this.mDB,
  1057.             "SELECT * FROM cal_events " +
  1058.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1059.             );
  1060.  
  1061.         this.mSelectTodoExceptions = createStatement (
  1062.             this.mDB,
  1063.             "SELECT * FROM cal_todos " +
  1064.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1065.             );
  1066.  
  1067.         // For the extra-item data, note that we use mDBTwo, so that
  1068.         // these can be executed while a selectItems is running!
  1069.         this.mSelectAttendeesForItem = createStatement(
  1070.             this.mDBTwo,
  1071.             "SELECT * FROM cal_attendees " +
  1072.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1073.             );
  1074.  
  1075.         this.mSelectAttendeesForItemWithRecurrenceId = createStatement(
  1076.             this.mDBTwo,
  1077.             "SELECT * FROM cal_attendees " +
  1078.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1079.             );
  1080.  
  1081.         this.mSelectPropertiesForItem = createStatement(
  1082.             this.mDBTwo,
  1083.             "SELECT * FROM cal_properties " +
  1084.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1085.             );
  1086.  
  1087.         this.mSelectPropertiesForItemWithRecurrenceId = createStatement(
  1088.             this.mDBTwo,
  1089.             "SELECT * FROM cal_properties " +
  1090.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1091.             );
  1092.  
  1093.         this.mSelectRecurrenceForItem = createStatement(
  1094.             this.mDBTwo,
  1095.             "SELECT * FROM cal_recurrence " +
  1096.             "WHERE item_id = :item_id " +
  1097.             "ORDER BY recur_index"
  1098.             );
  1099.  
  1100.         // insert statements
  1101.         this.mInsertEvent = createStatement (
  1102.             this.mDB,
  1103.             "INSERT INTO cal_events " +
  1104.             "  (cal_id, id, time_created, last_modified, " +
  1105.             "   title, priority, privacy, ical_status, flags, " +
  1106.             "   event_start, event_start_tz, event_end, event_end_tz, event_stamp, " +
  1107.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz) " +
  1108.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1109.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1110.             "        :event_start, :event_start_tz, :event_end, :event_end_tz, :event_stamp, " +
  1111.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz)"
  1112.             );
  1113.  
  1114.         this.mInsertTodo = createStatement (
  1115.             this.mDB,
  1116.             "INSERT INTO cal_todos " +
  1117.             "  (cal_id, id, time_created, last_modified, " +
  1118.             "   title, priority, privacy, ical_status, flags, " +
  1119.             "   todo_entry, todo_entry_tz, todo_due, todo_due_tz, todo_completed, " +
  1120.             "   todo_completed_tz, todo_complete, " +
  1121.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz) " +
  1122.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1123.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1124.             "        :todo_entry, :todo_entry_tz, :todo_due, :todo_due_tz, " +
  1125.             "        :todo_completed, :todo_completed_tz, :todo_complete, " +
  1126.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz)"
  1127.             );
  1128.         this.mInsertProperty = createStatement (
  1129.             this.mDB,
  1130.             "INSERT INTO cal_properties (item_id, recurrence_id, recurrence_id_tz, key, value) " +
  1131.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :key, :value)"
  1132.             );
  1133.         this.mInsertAttendee = createStatement (
  1134.             this.mDB,
  1135.             "INSERT INTO cal_attendees " +
  1136.             "  (item_id, recurrence_id, recurrence_id_tz, attendee_id, common_name, rsvp, role, status, type) " +
  1137.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :attendee_id, :common_name, :rsvp, :role, :status, :type)"
  1138.             );
  1139.         this.mInsertRecurrence = createStatement (
  1140.             this.mDB,
  1141.             "INSERT INTO cal_recurrence " +
  1142.             "  (item_id, recur_index, recur_type, is_negative, dates, count, end_date, interval, second, minute, hour, day, monthday, yearday, weekno, month, setpos) " +
  1143.             "VALUES (:item_id, :recur_index, :recur_type, :is_negative, :dates, :count, :end_date, :interval, :second, :minute, :hour, :day, :monthday, :yearday, :weekno, :month, :setpos)"
  1144.             );
  1145.  
  1146.         // delete statements
  1147.         this.mDeleteEvent = createStatement (
  1148.             this.mDB,
  1149.             "DELETE FROM cal_events WHERE id = :id"
  1150.             );
  1151.         this.mDeleteTodo = createStatement (
  1152.             this.mDB,
  1153.             "DELETE FROM cal_todos WHERE id = :id"
  1154.             );
  1155.         this.mDeleteAttendees = createStatement (
  1156.             this.mDB,
  1157.             "DELETE FROM cal_attendees WHERE item_id = :item_id"
  1158.             );
  1159.         this.mDeleteProperties = createStatement (
  1160.             this.mDB,
  1161.             "DELETE FROM cal_properties WHERE item_id = :item_id"
  1162.             );
  1163.         this.mDeleteRecurrence = createStatement (
  1164.             this.mDB,
  1165.             "DELETE FROM cal_recurrence WHERE item_id = :item_id"
  1166.             );
  1167.     },
  1168.  
  1169.  
  1170.     //
  1171.     // database reading functions
  1172.     //
  1173.  
  1174.     // read in the common ItemBase attributes from aDBRow, and stick
  1175.     // them on item
  1176.     getItemBaseFromRow: function (row, flags, item) {
  1177.         if (row.time_created)
  1178.             item.creationDate = newDateTime(row.time_created, "UTC");
  1179.         if (row.last_modified)
  1180.             item.lastModifiedTime = newDateTime(row.last_modified, "UTC");
  1181.         item.calendar = this;
  1182.         item.id = row.id;
  1183.         if (row.title)
  1184.             item.title = row.title;
  1185.         if (row.priority)
  1186.             item.priority = row.priority;
  1187.         if (row.privacy)
  1188.             item.privacy = row.privacy;
  1189.         if (row.ical_status)
  1190.             item.status = row.ical_status;
  1191.  
  1192.         if (row.alarm_time)
  1193.             item.alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
  1194.  
  1195.         if (row.recurrence_id)
  1196.             item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
  1197.  
  1198.         if (flags)
  1199.             flags.value = row.flags;
  1200.     },
  1201.  
  1202.     getEventFromRow: function (row, flags) {
  1203.         var item = new CalEvent();
  1204.  
  1205.         this.getItemBaseFromRow (row, flags, item);
  1206.  
  1207.         if (row.event_start)
  1208.             item.startDate = newDateTime(row.event_start, row.event_start_tz);
  1209.         if (row.event_end)
  1210.             item.endDate = newDateTime(row.event_end, row.event_end_tz);
  1211.         if (row.event_stamp)
  1212.             item.stampTime = newDateTime(row.event_stamp, "UTC");
  1213.         if ((row.flags & CAL_ITEM_FLAG_EVENT_ALLDAY) != 0) {
  1214.             item.startDate.isDate = true;
  1215.             item.endDate.isDate = true;
  1216.         }
  1217.  
  1218.         return item;
  1219.     },
  1220.  
  1221.     getTodoFromRow: function (row, flags) {
  1222.         var item = new CalTodo();
  1223.  
  1224.         this.getItemBaseFromRow (row, flags, item);
  1225.  
  1226.         if (row.todo_entry)
  1227.             item.entryDate = newDateTime(row.todo_entry, row.todo_entry_tz);
  1228.         if (row.todo_due)
  1229.             item.dueDate = newDateTime(row.todo_due, row.todo_due_tz);
  1230.         if (row.todo_completed)
  1231.             item.completedDate = newDateTime(row.todo_completed, row.todo_completed_tz);
  1232.         if (row.todo_complete)
  1233.             item.percentComplete = row.todo_complete;
  1234.  
  1235.         return item;
  1236.     },
  1237.  
  1238.     // after we get the base item, we need to check if we need to pull in
  1239.     // any extra data from other tables.  We do that here.
  1240.  
  1241.     // note that we use mDBTwo for this, so this can be run while a
  1242.     // select is executing; don't use any statements that aren't
  1243.     // against mDBTwo in here!
  1244.     
  1245.     getAdditionalDataForItem: function (item, flags) {
  1246.         if (flags & CAL_ITEM_FLAG_HAS_ATTENDEES) {
  1247.             var selectItem = null;
  1248.             if (item.recurrenceId == null)
  1249.                 selectItem = this.mSelectAttendeesForItem;
  1250.             else {
  1251.                 selectItem = this.mSelectAttendeesForItemWithRecurrenceId;
  1252.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1253.             }
  1254.  
  1255.             selectItem.params.item_id = item.id;
  1256.  
  1257.             while (selectItem.step()) {
  1258.                 var attendee = this.getAttendeeFromRow(selectItem.row);
  1259.                 item.addAttendee(attendee);
  1260.             }
  1261.             selectItem.reset();
  1262.         }
  1263.  
  1264.         var row;
  1265.         if (flags & CAL_ITEM_FLAG_HAS_PROPERTIES) {
  1266.             var selectItem = null;
  1267.             if (item.recurrenceId == null)
  1268.                 selectItem = this.mSelectPropertiesForItem;
  1269.             else {
  1270.                 selectItem = this.mSelectPropertiesForItemWithRecurrenceId;
  1271.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1272.             }
  1273.                 
  1274.             selectItem.params.item_id = item.id;
  1275.             
  1276.             while (selectItem.step()) {
  1277.                 row = selectItem.row;
  1278.                 item.setProperty (row.key, row.value);
  1279.             }
  1280.             selectItem.reset();
  1281.         }
  1282.  
  1283.         var i;
  1284.         if (flags & CAL_ITEM_FLAG_HAS_RECURRENCE) {
  1285.             if (item.recurrenceId)
  1286.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1287.  
  1288.             var rec = null;
  1289.  
  1290.             this.mSelectRecurrenceForItem.params.item_id = item.id;
  1291.             while (this.mSelectRecurrenceForItem.step()) {
  1292.                 row = this.mSelectRecurrenceForItem.row;
  1293.  
  1294.                 var ritem = null;
  1295.  
  1296.                 if (row.recur_type == null ||
  1297.                     row.recur_type == "x-dateset")
  1298.                 {
  1299.                     ritem = new CalRecurrenceDateSet();
  1300.  
  1301.                     var dates = row.dates.split(",");
  1302.                     for (i = 0; i < dates.length; i++) {
  1303.                         var date = textToDate(dates[i]);
  1304.                         ritem.addDate(date);
  1305.                     }
  1306.                 } else if (row.recur_type == "x-date") {
  1307.                     ritem = new CalRecurrenceDate();
  1308.                     var d = row.dates;
  1309.                     ritem.date = textToDate(d);
  1310.                 } else {
  1311.                     ritem = new CalRecurrenceRule();
  1312.  
  1313.                     ritem.type = row.recur_type;
  1314.                     if (row.count) {
  1315.                         ritem.count = row.count;
  1316.                     } else {
  1317.                         if (row.end_date)
  1318.                             ritem.endDate = newDateTime(row.end_date);
  1319.                         else
  1320.                             ritem.endDate = null;
  1321.                     }
  1322.                     ritem.interval = row.interval;
  1323.  
  1324.                     var rtypes = ["second",
  1325.                                   "minute",
  1326.                                   "hour",
  1327.                                   "day",
  1328.                                   "monthday",
  1329.                                   "yearday",
  1330.                                   "weekno",
  1331.                                   "month",
  1332.                                   "setpos"];
  1333.  
  1334.                     for (i = 0; i < rtypes.length; i++) {
  1335.                         var comp = "BY" + rtypes[i].toUpperCase();
  1336.                         if (row[rtypes[i]]) {
  1337.                             var rstr = row[rtypes[i]].toString().split(",");
  1338.                             var rarray = [];
  1339.                             for (var j = 0; j < rstr.length; j++) {
  1340.                                 rarray[j] = parseInt(rstr[j]);
  1341.                             }
  1342.  
  1343.                             ritem.setComponent (comp, rarray.length, rarray);
  1344.                         }
  1345.                     }
  1346.                 }
  1347.  
  1348.                 if (row.is_negative)
  1349.                     ritem.isNegative = true;
  1350.                 if (rec == null) {
  1351.                     rec = new CalRecurrenceInfo();
  1352.                     rec.item = item;
  1353.                 }
  1354.                 rec.appendRecurrenceItem(ritem);
  1355.             }
  1356.  
  1357.             if (rec == null) {
  1358.                 dump ("XXXX Expected to find recurrence, but got no items!\n");
  1359.             }
  1360.             item.recurrenceInfo = rec;
  1361.  
  1362.             this.mSelectRecurrenceForItem.reset();
  1363.         }
  1364.  
  1365.         if (flags & CAL_ITEM_FLAG_HAS_EXCEPTIONS) {
  1366.             if (item.recurrenceId)
  1367.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1368.  
  1369.             var rec = item.recurrenceInfo;
  1370.  
  1371.             var exceptions = [];
  1372.  
  1373.             if (item instanceof kCalIEvent) {
  1374.                 this.mSelectEventExceptions.params.id = item.id;
  1375.                 while (this.mSelectEventExceptions.step()) {
  1376.                     var row = this.mSelectEventExceptions.row;
  1377.                     var flags = {};
  1378.                     var exc = this.getEventFromRow(row, flags);
  1379.                     exceptions.push({item: exc, flags: flags.value});
  1380.                 }
  1381.                 this.mSelectEventExceptions.reset();
  1382.             } else if (item instanceof kCalITodo) {
  1383.                 this.mSelectTodoExceptions.params.id = item.id;
  1384.                 while (this.mSelectTodoExceptions.step()) {
  1385.                     var row = this.mSelectTodoExceptions.row;
  1386.                     var flags = {};
  1387.                     var exc = this.getTodoFromRow(row, flags);
  1388.                     
  1389.                     exceptions.push({item: exc, flags: flags.value});
  1390.                 }
  1391.                 this.mSelectTodoExceptions.reset();
  1392.             } else {
  1393.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1394.             }
  1395.  
  1396.             for each (var exc in exceptions) {
  1397.                 this.getAdditionalDataForItem(exc.item, exc.flags);
  1398.                 exc.item.parentItem = item;
  1399.                 rec.modifyException(exc.item);
  1400.             }
  1401.         }
  1402.     },
  1403.  
  1404.     getAttendeeFromRow: function (row) {
  1405.         var a = CalAttendee();
  1406.  
  1407.         a.id = row.attendee_id;
  1408.         a.commonName = row.common_name;
  1409.         a.rsvp = (row.rsvp != 0);
  1410.         a.role = row.role;
  1411.         a.participationStatus = row.status;
  1412.         a.userType = row.type;
  1413.  
  1414.         return a;
  1415.     },
  1416.  
  1417.     //
  1418.     // get item from db or from cache with given iid
  1419.     //
  1420.     getItemById: function (aID) {
  1421.         // cached?
  1422.         if (this.mItemCache[aID] != null)
  1423.             return this.mItemCache[aID];
  1424.  
  1425.         var retval = null;
  1426.  
  1427.         // not cached; need to read from the db
  1428.         var flags = {};
  1429.         var item = null;
  1430.  
  1431.         // try events first
  1432.         this.mSelectEvent.params.id = aID;
  1433.         if (this.mSelectEvent.step())
  1434.             item = this.getEventFromRow(this.mSelectEvent.row, flags);
  1435.         this.mSelectEvent.reset();
  1436.  
  1437.         // try todo if event fails
  1438.         if (!item) {
  1439.             this.mSelectTodo.params.id = aID;
  1440.             if (this.mSelectTodo.step())
  1441.                 item = this.getTodoFromRow(this.mSelectTodo.row, flags);
  1442.             this.mSelectTodo.reset();
  1443.         }
  1444.  
  1445.         // bail if still not found
  1446.         if (!item)
  1447.             return null;
  1448.  
  1449.         this.getAdditionalDataForItem(item, flags.value);
  1450.  
  1451.         item.makeImmutable();
  1452.  
  1453.         // cache it
  1454.         this.mItemCache[aID] = item;
  1455.  
  1456.         return item;
  1457.     },
  1458.  
  1459.     //
  1460.     // database writing functions
  1461.     //
  1462.  
  1463.     setDateParamHelper: function (params, entryname, cdt) {
  1464.         if (cdt) {
  1465.             params[entryname] = cdt.nativeTime;
  1466.             params[entryname + "_tz"] = cdt.timezone;
  1467.         } else {
  1468.             params[entryname] = null;
  1469.             params[entryname + "_tz"] = null;
  1470.         }
  1471.     },
  1472.  
  1473.     flushItem: function (item, olditem) {
  1474.         this.mDB.beginTransaction();
  1475.         try {
  1476.             this.writeItem(item, olditem);
  1477.             this.mDB.commitTransaction();
  1478.         } catch (e) {
  1479.             dump("flushItem DB error: " + this.mDB.lastErrorString + "\n");
  1480.             Components.utils.reportError("flushItem DB error: " +
  1481.                                          this.mDB.lastErrorString);
  1482.             this.mDB.rollbackTransaction();
  1483.             throw e;
  1484.         }
  1485.  
  1486.         this.mItemCache[item.id] = item;
  1487.     },
  1488.  
  1489.     //
  1490.     // Nuke olditem, if any
  1491.     //
  1492.  
  1493.     deleteOldItem: function (item, olditem) {
  1494.         if (olditem) {
  1495.             var oldItemDeleteStmt;
  1496.             if (olditem instanceof kCalIEvent)
  1497.                 oldItemDeleteStmt = this.mDeleteEvent;
  1498.             else if (olditem instanceof kCalITodo)
  1499.                 oldItemDeleteStmt = this.mDeleteTodo;
  1500.  
  1501.             oldItemDeleteStmt.params.id = olditem.id;
  1502.             this.mDeleteAttendees.params.item_id = olditem.id;
  1503.             this.mDeleteProperties.params.item_id = olditem.id;
  1504.             this.mDeleteRecurrence.params.item_id = olditem.id;
  1505.  
  1506.             this.mDeleteRecurrence.execute();
  1507.             this.mDeleteProperties.execute();
  1508.             this.mDeleteAttendees.execute();
  1509.             oldItemDeleteStmt.execute();
  1510.         }
  1511.     },
  1512.  
  1513.     //
  1514.     // The write* functions execute the database bits
  1515.     // to write the given item type.  They're to return
  1516.     // any bits they want or'd into flags, which will be passed
  1517.     // to writeEvent/writeTodo to actually do the writing.
  1518.     //
  1519.  
  1520.     writeItem: function (item, olditem) {
  1521.         var flags = 0;
  1522.  
  1523.         this.deleteOldItem(item, olditem);
  1524.  
  1525.         flags |= this.writeAttendees(item, olditem);
  1526.         flags |= this.writeRecurrence(item, olditem);
  1527.         flags |= this.writeProperties(item, olditem);
  1528.         flags |= this.writeAttachments(item, olditem);
  1529.  
  1530.         if (item instanceof kCalIEvent)
  1531.             this.writeEvent(item, olditem, flags);
  1532.         else if (item instanceof kCalITodo)
  1533.             this.writeTodo(item, olditem, flags);
  1534.         else
  1535.             throw Components.results.NS_ERROR_UNEXPECTED;
  1536.     },
  1537.  
  1538.     writeEvent: function (item, olditem, flags) {
  1539.         var ip = this.mInsertEvent.params;
  1540.         this.setupItemBaseParams(item, olditem,ip);
  1541.  
  1542.         var tmp;
  1543.  
  1544.         tmp = item.getUnproxiedProperty("DTSTART");
  1545.         //if (tmp instanceof kCalIDateTime) {}
  1546.         this.setDateParamHelper(ip, "event_start", tmp);
  1547.         tmp = item.getUnproxiedProperty("DTEND");
  1548.         //if (tmp instanceof kCalIDateTime) {}
  1549.         this.setDateParamHelper(ip, "event_end", tmp);
  1550.  
  1551.         if (item.startDate.isDate)
  1552.             flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
  1553.  
  1554.         ip.flags = flags;
  1555.  
  1556.         this.mInsertEvent.execute();
  1557.     },
  1558.  
  1559.     writeTodo: function (item, olditem, flags) {
  1560.         var ip = this.mInsertTodo.params;
  1561.  
  1562.         this.setupItemBaseParams(item, olditem,ip);
  1563.  
  1564.         this.setDateParamHelper(ip, "todo_entry", item.getUnproxiedProperty("DTSTART"));
  1565.         this.setDateParamHelper(ip, "todo_due", item.getUnproxiedProperty("DUE"));
  1566.         this.setDateParamHelper(ip, "todo_completed", item.getUnproxiedProperty("COMPLETED"));
  1567.  
  1568.         ip.todo_complete = item.getUnproxiedProperty("PERCENT-COMPLETED");
  1569.  
  1570.         ip.flags = flags;
  1571.  
  1572.         this.mInsertTodo.execute();
  1573.     },
  1574.  
  1575.     setupItemBaseParams: function (item, olditem, ip) {
  1576.         ip.cal_id = this.mCalId;
  1577.         ip.id = item.id;
  1578.  
  1579.         if (item.recurrenceId)
  1580.             this.setDateParamHelper(ip, "recurrence_id", item.recurrenceId);
  1581.  
  1582.         var tmp;
  1583.  
  1584.         if (tmp = item.getUnproxiedProperty("CREATED"))
  1585.             ip.time_created = tmp.nativeTime;
  1586.         if (tmp = item.getUnproxiedProperty("LAST-MODIFIED"))
  1587.             ip.last_modified = tmp.nativeTime;
  1588.  
  1589.         ip.title = item.getUnproxiedProperty("SUMMARY");
  1590.         ip.priority = item.getUnproxiedProperty("PRIORITY");
  1591.         ip.privacy = item.getUnproxiedProperty("CLASS");
  1592.         ip.ical_status = item.getUnproxiedProperty("STATUS");
  1593.  
  1594.         if (!item.parentItem)
  1595.             ip.event_stamp = item.stampTime.nativeTime;
  1596.  
  1597.         if (tmp = item.getUnproxiedProperty("ALARMTIME"))
  1598.             this.setDateParamHelper(ip, "alarm_time", tmp);
  1599.     },
  1600.  
  1601.     writeAttendees: function (item, olditem) {
  1602.         // XXX how does this work for proxy stuffs?
  1603.         var attendees = item.getAttendees({});
  1604.         if (attendees && attendees.length > 0) {
  1605.             flags |= CAL_ITEM_FLAG_HAS_ATTENDEES;
  1606.             for each (var att in attendees) {
  1607.                 var ap = this.mInsertAttendee.params;
  1608.                 ap.item_id = item.id;
  1609.                 this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
  1610.                 ap.attendee_id = att.id;
  1611.                 ap.common_name = att.commonName;
  1612.                 ap.rsvp = att.rsvp;
  1613.                 ap.role = att.role;
  1614.                 ap.status = att.participationStatus;
  1615.                 ap.type = att.userType;
  1616.  
  1617.                 this.mInsertAttendee.execute();
  1618.             }
  1619.  
  1620.             return CAL_ITEM_FLAG_HAS_ATTENDEES;
  1621.         }
  1622.  
  1623.         return 0;
  1624.     },
  1625.  
  1626.     writeProperties: function (item, olditem) {
  1627.         var ret = 0;
  1628.         var propEnumerator = item.unproxiedPropertyEnumerator;
  1629.         while (propEnumerator.hasMoreElements()) {
  1630.             ret = CAL_ITEM_FLAG_HAS_PROPERTIES;
  1631.  
  1632.             var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
  1633.  
  1634.             if (item.isPropertyPromoted(prop.name))
  1635.                 continue;
  1636.  
  1637.             var pp = this.mInsertProperty.params;
  1638.  
  1639.             pp.key = prop.name;
  1640.             var pval = prop.value;
  1641.             if (pval instanceof kCalIDateTime) {
  1642.                 pp.value = pval.nativeTime;
  1643.             } else {
  1644.                 pp.value = pval;
  1645.             }
  1646.             pp.item_id = item.id;
  1647.             this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
  1648.  
  1649.             this.mInsertProperty.execute();
  1650.         }
  1651.  
  1652.         return ret;
  1653.     },
  1654.  
  1655.     writeRecurrence: function (item, olditem) {
  1656.         var flags = 0;
  1657.  
  1658.         var rec = item.recurrenceInfo;
  1659.         if (rec) {
  1660.             flags = CAL_ITEM_FLAG_HAS_RECURRENCE;
  1661.             var ritems = rec.getRecurrenceItems ({});
  1662.             for (i in ritems) {
  1663.                 var ritem = ritems[i];
  1664.                 ap = this.mInsertRecurrence.params;
  1665.                 ap.item_id = item.id;
  1666.                 ap.recur_index = i;
  1667.                 ap.is_negative = ritem.isNegative;
  1668.                 if (ritem instanceof kCalIRecurrenceDate) {
  1669.                     ritem = ritem.QueryInterface(kCalIRecurrenceDate);
  1670.                     ap.recur_type = "x-date";
  1671.                     ap.dates = dateToText(ritem.date);
  1672.  
  1673.                 } else if (ritem instanceof kCalIRecurrenceDateSet) {
  1674.                     ritem = ritem.QueryInterface(kCalIRecurrenceDateSet);
  1675.                     ap.recur_type = "x-dateset";
  1676.  
  1677.                     var rdates = ritem.getDates({});
  1678.                     var datestr = "";
  1679.                     for (j in rdates) {
  1680.                         if (j != 0)
  1681.                             datestr += ",";
  1682.  
  1683.                         datestr += dateToText(rdates[j]);
  1684.                     }
  1685.  
  1686.                     ap.dates = datestr;
  1687.  
  1688.                 } else if (ritem instanceof kCalIRecurrenceRule) {
  1689.                     ritem = ritem.QueryInterface(kCalIRecurrenceRule);
  1690.                     ap.recur_type = ritem.type;
  1691.  
  1692.                     if (ritem.isByCount)
  1693.                         ap.count = ritem.count;
  1694.                     else
  1695.                         ap.end_date = ritem.endDate ? ritem.endDate.nativeTime : null;
  1696.  
  1697.                     ap.interval = ritem.interval;
  1698.  
  1699.                     var rtypes = ["second",
  1700.                                   "minute",
  1701.                                   "hour",
  1702.                                   "day",
  1703.                                   "monthday",
  1704.                                   "yearday",
  1705.                                   "weekno",
  1706.                                   "month",
  1707.                                   "setpos"];
  1708.                     for (j = 0; j < rtypes.length; j++) {
  1709.                         var comp = "BY" + rtypes[j].toUpperCase();
  1710.                         var comps = ritem.getComponent(comp, {});
  1711.                         if (comps && comps.length > 0) {
  1712.                             var compstr = comps.join(",");
  1713.                             ap[rtypes[j]] = compstr;
  1714.                         }
  1715.                     }
  1716.                 } else {
  1717.                     dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
  1718.                 }
  1719.  
  1720.                 this.mInsertRecurrence.execute();
  1721.             }
  1722.  
  1723.             var exceptions = rec.getExceptionIds ({});
  1724.             if (exceptions.length > 0) {
  1725.                 flags |= CAL_ITEM_FLAG_HAS_EXCEPTIONS;
  1726.  
  1727.                 // we need to serialize each exid as a separate
  1728.                 // event/todo; setupItemBase will handle
  1729.                 // writing the recurrenceId for us
  1730.                 for each (exid in exceptions) {
  1731.                     var ex = rec.getExceptionFor(exid, false);
  1732.                     if (!ex)
  1733.                         throw Components.results.NS_ERROR_UNEXPECTED;
  1734.                     this.writeItem(ex, null);
  1735.                 }
  1736.             }
  1737.         }
  1738.  
  1739.         return flags;
  1740.     },
  1741.  
  1742.     writeAttachments: function (item, olditem) {
  1743.         // XXX write me?
  1744.         return 0;
  1745.     },
  1746.  
  1747.     //
  1748.     // delete the item with the given uid
  1749.     //
  1750.     deleteItemById: function (aID) {
  1751.         this.mDB.beginTransaction();
  1752.         try {
  1753.             this.mDeleteAttendees(aID);
  1754.             this.mDeleteProperties(aID);
  1755.             this.mDeleteRecurrence(aID);
  1756.             this.mDeleteEvent(aID);
  1757.             this.mDeleteTodo(aID);
  1758.             this.mDB.commitTransaction();
  1759.         } catch (e) {
  1760.             Components.utils.reportError("deleteItemById DB error: " + 
  1761.                                          this.mDB.lastErrorString);
  1762.             this.mDB.rollbackTransaction();
  1763.             throw e;
  1764.         }
  1765.  
  1766.         delete this.mItemCache[aID];
  1767.     }
  1768. }
  1769.  
  1770.  
  1771. /****
  1772.  **** module registration
  1773.  ****/
  1774.  
  1775. var calStorageCalendarModule = {
  1776.     mCID: Components.ID("{b3eaa1c4-5dfe-4c0a-b62a-b3a514218461}"),
  1777.     mContractID: "@mozilla.org/calendar/calendar;1?type=storage",
  1778.     
  1779.     registerSelf: function (compMgr, fileSpec, location, type) {
  1780.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1781.         compMgr.registerFactoryLocation(this.mCID,
  1782.                                         "Calendar mozStorage/SQL back-end",
  1783.                                         this.mContractID,
  1784.                                         fileSpec,
  1785.                                         location,
  1786.                                         type);
  1787.     },
  1788.  
  1789.     getClassObject: function (compMgr, cid, iid) {
  1790.         if (!cid.equals(this.mCID))
  1791.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1792.  
  1793.         if (!iid.equals(Components.interfaces.nsIFactory))
  1794.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1795.  
  1796.         if (!kStorageServiceIID)
  1797.             throw Components.results.NS_ERROR_NOT_INITIALIZED;
  1798.  
  1799.         if (!CalEvent) {
  1800.             initCalStorageCalendarComponent();
  1801.         }
  1802.  
  1803.         return this.mFactory;
  1804.     },
  1805.  
  1806.     mFactory: {
  1807.         createInstance: function (outer, iid) {
  1808.             if (outer != null)
  1809.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  1810.             return (new calStorageCalendar()).QueryInterface(iid);
  1811.         }
  1812.     },
  1813.  
  1814.     canUnload: function(compMgr) {
  1815.         return true;
  1816.     }
  1817. };
  1818.  
  1819. function NSGetModule(compMgr, fileSpec) {
  1820.     return calStorageCalendarModule;
  1821. }
  1822.  
  1823.  
  1824.  
  1825. //
  1826. // sqlTables generated from schema.sql via makejsschema.pl
  1827. //
  1828.  
  1829. var sqlTables = {
  1830.   cal_calendar_schema_version:
  1831.     "    version    INTEGER" +
  1832.     "",
  1833.  
  1834.   cal_events:
  1835.     /*     REFERENCES cal_calendars.id, */
  1836.     "    cal_id        INTEGER, " +
  1837.     /*  ItemBase bits */
  1838.     "    id        STRING," +
  1839.     "    time_created    INTEGER," +
  1840.     "    last_modified    INTEGER," +
  1841.     "    title        STRING," +
  1842.     "    priority    INTEGER," +
  1843.     "    privacy        STRING," +
  1844.     "    ical_status    STRING," +
  1845.     "    recurrence_id    INTEGER," +
  1846.     "    recurrence_id_tz    VARCHAR," +
  1847.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  1848.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  1849.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  1850.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  1851.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  1852.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  1853.     "    flags        INTEGER," +
  1854.     /*  Event bits */
  1855.     "    event_start    INTEGER," +
  1856.     "    event_start_tz    VARCHAR," +
  1857.     "    event_end    INTEGER," +
  1858.     "    event_end_tz    VARCHAR," +
  1859.     "    event_stamp    INTEGER," +
  1860.     /*  alarm time */
  1861.     "    alarm_time    INTEGER," +
  1862.     "    alarm_time_tz    VARCHAR" +
  1863.     "",
  1864.  
  1865.   cal_todos:
  1866.     /*     REFERENCES cal_calendars.id, */
  1867.     "    cal_id        INTEGER, " +
  1868.     /*  ItemBase bits */
  1869.     "    id        STRING," +
  1870.     "    time_created    INTEGER," +
  1871.     "    last_modified    INTEGER," +
  1872.     "    title        STRING," +
  1873.     "    priority    INTEGER," +
  1874.     "    privacy        STRING," +
  1875.     "    ical_status    STRING," +
  1876.     "    recurrence_id    INTEGER," +
  1877.     "    recurrence_id_tz    VARCHAR," +
  1878.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  1879.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  1880.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  1881.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  1882.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  1883.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  1884.     "    flags        INTEGER," +
  1885.     /*  Todo bits */
  1886.     /*  date the todo is to be displayed */
  1887.     "    todo_entry    INTEGER," +
  1888.     "    todo_entry_tz    VARCHAR," +
  1889.     /*  date the todo is due */
  1890.     "    todo_due    INTEGER," +
  1891.     "    todo_due_tz    VARCHAR," +
  1892.     /*  date the todo is completed */
  1893.     "    todo_completed    INTEGER," +
  1894.     "    todo_completed_tz VARCHAR," +
  1895.     /*  percent the todo is complete (0-100) */
  1896.     "    todo_complete    INTEGER," +
  1897.     /*  alarm time */
  1898.     "    alarm_time    INTEGER," +
  1899.     "    alarm_time_tz    VARCHAR" +
  1900.     "",
  1901.  
  1902.   cal_attendees:
  1903.     "    item_id         STRING," +
  1904.     "    recurrence_id    INTEGER," +
  1905.     "    recurrence_id_tz    VARCHAR," +
  1906.     "    attendee_id    STRING," +
  1907.     "    common_name    STRING," +
  1908.     "    rsvp        INTEGER," +
  1909.     "    role        STRING," +
  1910.     "    status        STRING," +
  1911.     "    type        STRING" +
  1912.     "",
  1913.  
  1914.   cal_recurrence:
  1915.     "    item_id        STRING," +
  1916.     /*  the index in the recurrence array of this thing */
  1917.     "    recur_index    INTEGER, " +
  1918.     /*  values from calIRecurrenceInfo; if null, date-based. */
  1919.     "    recur_type    STRING, " +
  1920.     "    is_negative    BOOLEAN," +
  1921.     /*  */
  1922.     /*  these are for date-based recurrence */
  1923.     /*  */
  1924.     /*  comma-separated list of dates */
  1925.     "    dates        STRING," +
  1926.     /*  */
  1927.     /*  these are for rule-based recurrence */
  1928.     /*  */
  1929.     "    count        INTEGER," +
  1930.     "    end_date    INTEGER," +
  1931.     "    interval    INTEGER," +
  1932.     /*  components, comma-separated list or null */
  1933.     "    second        STRING," +
  1934.     "    minute        STRING," +
  1935.     "    hour        STRING," +
  1936.     "    day        STRING," +
  1937.     "    monthday    STRING," +
  1938.     "    yearday        STRING," +
  1939.     "    weekno        STRING," +
  1940.     "    month        STRING," +
  1941.     "    setpos        STRING" +
  1942.     "",
  1943.  
  1944.   cal_properties:
  1945.     "    item_id        STRING," +
  1946.     "    recurrence_id    INTEGER," +
  1947.     "    recurrence_id_tz    VARCHAR," +
  1948.     "    key        STRING," +
  1949.     "    value        BLOB" +
  1950.     "",
  1951.  
  1952. };
  1953.