home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2006 June / PCpro_2006_06.ISO / files / firefox / calendar_windows_latest.xpi / components / calDavCalendar.js < prev    next >
Encoding:
Text File  |  2006-01-21  |  31.4 KB  |  905 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.  *   Dan Mosedale <dan.mosedale@oracle.com>
  25.  *   Mike Shaver <mike.x.shaver@oracle.com>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. //
  42. // calDavCalendar.js
  43. //
  44.  
  45. // XXXdmose deal with generation goop
  46.  
  47. // XXXdmose need to re-query for add & modify to get up-to-date items
  48.  
  49. // XXXdmose deal with etags
  50.  
  51. // XXXdmose deal with locking
  52.  
  53. // XXXdmose need to make and use better error reporting interface for webdav
  54. // (all uses of aStatusCode, probably)
  55.  
  56. // XXXdmose use real calendar result codes, not NS_ERROR_FAILURE for everything
  57.  
  58. const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
  59.  
  60. function debug(s) {
  61.     const debugging = true;
  62.     if (debugging) {
  63.         dump(s);
  64.     }
  65. }
  66.  
  67. function calDavCalendar() {
  68.     this.wrappedJSObject = this;
  69.     this.mObservers = Array();
  70. }
  71.  
  72. // some shorthand
  73. const nsIWebDAVOperationListener = 
  74.     Components.interfaces.nsIWebDAVOperationListener;
  75. const calICalendar = Components.interfaces.calICalendar;
  76. const nsISupportsCString = Components.interfaces.nsISupportsCString;
  77. const calIEvent = Components.interfaces.calIEvent;
  78. const calIItemBase = Components.interfaces.calIItemBase;
  79. const calITodo = Components.interfaces.calITodo;
  80. const calEventClass = Components.classes["@mozilla.org/calendar/event;1"];
  81.  
  82. const kCalCalendarManagerContractID = "@mozilla.org/calendar/manager;1";
  83. const kCalICalendarManager = Components.interfaces.calICalendarManager;
  84.  
  85.  
  86. function makeOccurrence(item, start, end)
  87. {
  88.     var occ = item.createProxy();
  89.     occ.recurrenceId = start;
  90.     occ.startDate = start;
  91.     occ.endDate = end;
  92.  
  93.     return occ;
  94. }
  95.  
  96. var activeCalendarManager = null;
  97. function getCalendarManager()
  98. {
  99.     if (!activeCalendarManager) {
  100.         activeCalendarManager = 
  101.             Components.classes[kCalCalendarManagerContractID].getService(kCalICalendarManager);
  102.     }
  103.     return activeCalendarManager;
  104. }
  105.   
  106. // END_OF_TIME needs to be the max value a PRTime can be
  107. const START_OF_TIME = -0x7fffffffffffffff;
  108. const END_OF_TIME = 0x7fffffffffffffff;
  109.  
  110. calDavCalendar.prototype = {
  111.     //
  112.     // nsISupports interface
  113.     // 
  114.     QueryInterface: function (aIID) {
  115.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  116.             !aIID.equals(calICalendar)) {
  117.             throw Components.results.NS_ERROR_NO_INTERFACE;
  118.         }
  119.  
  120.         return this;
  121.     },
  122.  
  123.     //
  124.     // nsICalendar interface
  125.     //
  126.  
  127.     // attribute AUTF8String name;
  128.     get name() {
  129.         return getCalendarManager().getCalendarPref(this, "NAME");
  130.     },
  131.     set name(name) {
  132.         getCalendarManager().setCalendarPref(this, "NAME", name);
  133.     },
  134.  
  135.     // readonly attribute AUTF8String type;
  136.     get type() { return "caldav"; },
  137.  
  138.     mReadOnly: false,
  139.  
  140.     get readOnly() { 
  141.         return this.mReadOnly;
  142.     },
  143.     set readOnly(bool) {
  144.         this.mReadOnly = bool;
  145.     },
  146.  
  147.     // attribute nsIURI uri;
  148.     mUri: null,
  149.     get uri() { return this.mUri; },
  150.     set uri(aUri) { this.mUri = aUri; },
  151.  
  152.     get mCalendarUri() { 
  153.         calUri = this.mUri.clone();
  154.         calUri.spec += "/";
  155.         return calUri;
  156.     },
  157.  
  158.     refresh: function() {
  159.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  160.     },
  161.  
  162.     // attribute boolean suppressAlarms;
  163.     get suppressAlarms() { return false; },
  164.     set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
  165.  
  166.     // void addObserver( in calIObserver observer );
  167.     addObserver: function (aObserver, aItemFilter) {
  168.         for each (obs in aObserver) {
  169.             if (obs == aObserver) {
  170.                 return;
  171.             }
  172.         }
  173.  
  174.         this.mObservers.push(aObserver);
  175.     },
  176.  
  177.     // void removeObserver( in calIObserver observer );
  178.     removeObserver: function (aObserver) {
  179.         this.mObservers = this.mObservers.filter(
  180.             function(o) {return (o != aObserver);});
  181.     },
  182.  
  183.     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
  184.     addItem: function (aItem, aListener) {
  185.         var newItem = aItem.clone();
  186.         return this.adoptItem(newItem, aListener);
  187.     },
  188.  
  189.     // void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
  190.     adoptItem: function (aItem, aListener) {
  191.         if (this.readOnly) {
  192.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  193.         }
  194.  
  195.         if (aItem.id == null && aItem.isMutable)
  196.             // XXX real UUID here!!
  197.             aItem.id = "uuid" + (new Date()).getTime();
  198.  
  199.         if (aItem.id == null) {
  200.             if (aListener)
  201.                 aListener.onOperationComplete (this,
  202.                                                Components.results.NS_ERROR_FAILURE,
  203.                                                aListener.ADD,
  204.                                                aItem.id,
  205.                                                "Can't set ID on non-mutable item to addItem");
  206.             return;
  207.         }
  208.  
  209.         // XXX how are we REALLY supposed to figure this out?
  210.         var itemUri = this.mCalendarUri.clone();
  211.         itemUri.spec = itemUri.spec + aItem.id + ".ics";
  212.         debug("itemUri.spec = " + itemUri.spec + "\n");
  213.         var eventResource = new WebDavResource(itemUri);
  214.  
  215.         var listener = new WebDavListener();
  216.         var thisCalendar = this;
  217.         listener.onOperationComplete = 
  218.         function onPutComplete(aStatusCode, aResource, aOperation, aClosure) {
  219.  
  220.             // 201 = HTTP "Created"
  221.             //
  222.             if (aStatusCode == 201) {
  223.                 debug("Item added successfully\n");
  224.  
  225.                 var retVal = Components.results.NS_OK;
  226.  
  227.             } else if (aStatusCode == 200) {
  228.                 // XXXdmose once we get etag stuff working, this should
  229.                 // 
  230.                 debug("200 received from clients, until we have etags working"
  231.                       + " this probably means a collision; after that it'll"
  232.                       + " mean server malfunction/n");
  233.                 retVal = Components.results.NS_ERROR_FAILURE;
  234.             } else {
  235.                 if (aStatusCode > 999) {
  236.                     aStatusCode = "0x" + aStatusCode.toString(16);
  237.                 }
  238.  
  239.                 // XXX real error handling
  240.                 debug("Error adding item: " + aStatusCode + "\n");
  241.                 retVal = Components.results.NS_ERROR_FAILURE;
  242.             }
  243.  
  244.             // notify the listener
  245.             if (aListener) {
  246.                 try {
  247.                     aListener.onOperationComplete(thisCalendar,
  248.                                                   retVal,
  249.                                                   aListener.ADD,
  250.                                                   newItem.id,
  251.                                                   newItem);
  252.                 } catch (ex) {
  253.                     debug("addItem's onOperationComplete threw an exception "
  254.                           + ex + "; ignoring\n");
  255.                 }
  256.             }
  257.  
  258.             // notify observers
  259.             if (Components.isSuccessCode(retVal)) {
  260.                 thisCalendar.observeAddItem(newItem);
  261.             }
  262.         }
  263.   
  264.         aItem.calendar = this;
  265.         aItem.generation = 1;
  266.         aItem.setProperty("locationURI", itemUri.spec);
  267.         aItem.makeImmutable();
  268.  
  269.         debug("icalString = " + aItem.icalString + "\n");
  270.  
  271.         // XXX use if not exists
  272.         // do WebDAV put
  273.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  274.             .getService(Components.interfaces.nsIWebDAVService);
  275.         webSvc.putFromString(eventResource, "text/calendar", 
  276.                              aItem.icalString, listener, null);
  277.  
  278.         return;
  279.     },
  280.  
  281.     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
  282.     modifyItem: function modifyItem(aNewItem, aOldItem, aListener) {
  283.         if (this.readOnly) {
  284.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  285.         }
  286.  
  287.         if (aNewItem.id == null) {
  288.  
  289.             // XXXYYY fix to match iface spec
  290.             // this is definitely an error
  291.             if (aListener) {
  292.                 try {
  293.                     aListener.onOperationComplete(this,
  294.                                                   Components.results.NS_ERROR_FAILURE,
  295.                                                   aListener.MODIFY,
  296.                                                   aItem.id,
  297.                                                   "ID for modifyItem doesn't exist or is null");
  298.                 } catch (ex) {
  299.                     debug("modifyItem's onOperationComplete threw an"
  300.                           + " exception " + ex + "; ignoring\n");
  301.                 }
  302.             }
  303.  
  304.             return;
  305.         }
  306.  
  307.         var eventUri = this.mCalendarUri.clone();
  308.         try {
  309.             eventUri.spec = aNewItem.getProperty("locationURI");
  310.             debug("using locationURI: " + eventUri.spec + "\n");
  311.         } catch (ex) {
  312.             // XXX how are we REALLY supposed to figure this out?
  313.             eventUri.spec = eventUri.spec + aNewItem.id + ".ics";
  314.         }
  315.  
  316.         var eventResource = new WebDavResource(eventUri);
  317.  
  318.         var listener = new WebDavListener();
  319.         var thisCalendar = this;
  320.         listener.onOperationComplete = function(aStatusCode, aResource,
  321.                                                 aOperation, aClosure) {
  322.  
  323.             // 201 = HTTP "Created"
  324.             // 204 = HTTP "No Content"
  325.             //
  326.             if (aStatusCode == 204 || aStatusCode == 201) {
  327.                 debug("Item modified successfully.\n");
  328.                 var retVal = Components.results.NS_OK;
  329.  
  330.             } else {
  331.                 if (aStatusCode > 999) {
  332.                     aStatusCode = "0x " + aStatusCode.toString(16);
  333.                 }
  334.                 debug("Error modifying item: " + aStatusCode + "\n");
  335.  
  336.                 // XXX deal with non-existent item here, other
  337.                 // real error handling
  338.  
  339.                 // XXX aStatusCode will be 201 Created for a PUT on an item
  340.                 // that didn't exist before.
  341.  
  342.                 retVal = Components.results.NS_ERROR_FAILURE;
  343.             }
  344.  
  345.             // XXX ensure immutable version returned
  346.             // notify listener
  347.             if (aListener) {
  348.                 try {
  349.                     aListener.onOperationComplete(thisCalendar, retVal,
  350.                                                   aListener.MODIFY,
  351.                                                   aNewItem.id, aNewItem);
  352.                 } catch (ex) {
  353.                     debug("modifyItem's onOperationComplete threw an"
  354.                           + " exception " + ex + "; ignoring\n");
  355.                 }
  356.             }
  357.  
  358.             // notify observers
  359.             if (Components.isSuccessCode(retVal)) {
  360.                 thisCalendar.observeModifyItem(aNewItem, aOldItem);
  361.             }
  362.  
  363.             return;
  364.         }
  365.  
  366.         // XXX use if-exists stuff here
  367.         // XXX use etag as generation
  368.         // do WebDAV put
  369.         debug("modifyItem: aNewItem.icalString = " + aNewItem.icalString 
  370.               + "\n");
  371.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  372.             .getService(Components.interfaces.nsIWebDAVService);
  373.         webSvc.putFromString(eventResource, "text/calendar",
  374.                              aNewItem.icalString, listener, null);
  375.  
  376.         return;
  377.     },
  378.  
  379.  
  380.     // void deleteItem( in calIItemBase aItem, in calIOperationListener aListener );
  381.     deleteItem: function (aItem, aListener) {
  382.         if (this.readOnly) {
  383.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  384.         }
  385.  
  386.         if (aItem.id == null) {
  387.             if (aListener)
  388.                 aListener.onOperationComplete (this,
  389.                                                Components.results.NS_ERROR_FAILURE,
  390.                                                aListener.DELETE,
  391.                                                aItem.id,
  392.                                                "ID doesn't exist for deleteItem");
  393.             return;
  394.         }
  395.  
  396.         // XXX how are we REALLY supposed to figure this out?
  397.         var eventUri = this.mCalendarUri.clone();
  398.         eventUri.spec = eventUri.spec + aItem.id + ".ics";
  399.         var eventResource = new WebDavResource(eventUri);
  400.  
  401.         var listener = new WebDavListener();
  402.         var thisCalendar = this;
  403.  
  404.         listener.onOperationComplete = 
  405.         function onOperationComplete(aStatusCode, aResource, aOperation,
  406.                                      aClosure) {
  407.  
  408.             // 204 = HTTP "No content"
  409.             //
  410.             if (aStatusCode == 204) {
  411.                 debug("Item deleted successfully.\n");
  412.                 var retVal = Components.results.NS_OK;
  413.             } else {
  414.                 debug("Error deleting item: " + aStatusCode + "\n");
  415.                 // XXX real error handling here
  416.                 retVal = Components.results.NS_ERROR_FAILURE;
  417.             }
  418.  
  419.             // notify the listener
  420.             if (aListener) {
  421.                 try {
  422.                     aListener.onOperationComplete(thisCalendar,
  423.                                                   Components.results.NS_OK,
  424.                                                   aListener.DELETE,
  425.                                                   aItem.id,
  426.                                                   null);
  427.                 } catch (ex) {
  428.                     debug("deleteItem's onOperationComplete threw an"
  429.                           + " exception " + ex + "; ignoring\n");
  430.                 }
  431.             }
  432.  
  433.             // notify observers
  434.             if (Components.isSuccessCode(retVal)) {
  435.                 thisCalendar.observeDeleteItem(aItem);
  436.             }
  437.         }
  438.  
  439.         // XXX check etag/generation
  440.         // do WebDAV remove
  441.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  442.             .getService(Components.interfaces.nsIWebDAVService);
  443.         webSvc.remove(eventResource, listener, null);
  444.  
  445.         return;
  446.     },
  447.  
  448.     // void getItem( in string id, in calIOperationListener aListener );
  449.     getItem: function (aId, aListener) {
  450.  
  451.         if (!aListener)
  452.             return;
  453.  
  454.         if (aId == null) {
  455.             aListener.onOperationComplete(this,
  456.                                           Components.results.NS_ERROR_FAILURE,
  457.                                           aListener.GET,
  458.                                           null,
  459.                                           "passed in empty iid");
  460.             return;
  461.         }
  462.  
  463.  
  464.         // this is our basic search-by-uid xml
  465.         // XXX get rid of vevent filter?
  466.         // XXX need a prefix in the namespace decl?
  467.         default xml namespace = "urn:ietf:params:xml:ns:caldav";
  468.         var D = new Namespace("D", "DAV:");
  469.         queryXml = 
  470.           <calendar-query xmlns:D="DAV:">
  471.             <D:prop>
  472.               <calendar-data/>
  473.             </D:prop>
  474.             <filter>
  475.               <comp-filter name="VCALENDAR">
  476.                 <comp-filter name="VEVENT">
  477.                   <prop-filter name="UID">
  478.                     <text-match caseless="no">
  479.                       {aId}
  480.                     </text-match>
  481.                   </prop-filter>
  482.                 </comp-filter>
  483.               </comp-filter>
  484.             </filter>
  485.           </calendar-query>;
  486.  
  487.         this.reportInternal(xmlHeader + queryXml.toXMLString(), 
  488.                             false, null, null, 1, aListener);
  489.         return;
  490.     },
  491.  
  492.     reportInternal: function (aQuery, aOccurrences, aRangeStart, aRangeEnd, aCount, aListener)
  493.     {
  494.         var reportListener = new WebDavListener();
  495.         var count = 0;  // maximum number of hits to return
  496.         var thisCalendar = this; // need to access from inside the callback
  497.  
  498.         reportListener.onOperationDetail = function(aStatusCode, aResource,
  499.                                                     aOperation, aDetail,
  500.                                                     aClosure) {
  501.             var rv;
  502.             var errString;
  503.  
  504.             // is this detail the entire search result, rather than a single
  505.             // detail within a set?
  506.             //
  507.             if (aResource.path == calendarDirUri.path) {
  508.                 // XXX is this even valid?  what should we do here?
  509.                 // XXX if it's an error, it might be valid?
  510.                 throw("XXX report result for calendar, not event\n");
  511.             }
  512.  
  513.             var items = null;
  514.  
  515.             // XXX need to do better than looking for just 200
  516.             if (aStatusCode == 200) {
  517.  
  518.                 // we've already called back the maximum number of hits, so 
  519.                 // we're done here.
  520.                 // 
  521.                 if (aCount && count >= aCount) {
  522.                     return;
  523.                 }
  524.                 ++count;
  525.  
  526.                 // aDetail is the response element from the multi-status
  527.                 // XXX try-catch
  528.                 var xSerializer = Components.classes
  529.                     ['@mozilla.org/xmlextras/xmlserializer;1']
  530.                     .getService(Components.interfaces.nsIDOMSerializer);
  531.                 var response = xSerializer.serializeToString(aDetail);
  532.                 var responseElement = new XML(response);
  533.  
  534.                 // create calIItemBase from e4x object
  535.                 // XXX error-check that we only have one result, etc
  536.                 var C = new Namespace("urn:ietf:params:xml:ns:caldav");
  537.                 var D = new Namespace("DAV:");
  538.  
  539.                 // create a local event item
  540.                 // XXX not just events
  541.                 var item = calEventClass.createInstance(calIEvent);
  542.  
  543.                 // cause returned data to be parsed into the event item
  544.                 var calData = responseElement..C::["calendar-data"];
  545.                 if (!calData.toString().length) {
  546.                   Components.utils.reportError(
  547.                     "Empty or non-existent <calendar-data> element returned" +
  548.                     " by CalDAV server for URI <" + aResource.spec +
  549.                     ">; ignoring");
  550.                   return;
  551.                 }
  552.                 debug("item result = \n" + calData + "\n");
  553.                 // XXX try-catch
  554.                 item.icalString = calData;
  555.                 item.calendar = thisCalendar;
  556.  
  557.                 // save the location name in case we need to modify
  558.                 item.setProperty("locationURI", aResource.spec);
  559.                 debug("locationURI = " + aResource.spec + "\n");
  560.                 item.makeImmutable();
  561.  
  562.                 // figure out what type of item to return
  563.                 var iid;
  564.                 if(aOccurrences) {
  565.                     iid = calIItemBase;
  566.                     if (item.recurrenceInfo) {
  567.                         debug("ITEM has recurrence: " + item + " (" + item.title + ")\n");
  568.                         debug("rangestart: " + aRangeStart.jsDate + " -> " + aRangeEnd.jsDate + "\n");
  569.                         // XXX does getOcc call makeImmutable?
  570.                         items = item.recurrenceInfo.getOccurrences(aRangeStart,
  571.                                                                    aRangeEnd,
  572.                                                                    0, {});
  573.                     } else {
  574.                         // XXX need to make occurrences immutable?
  575.                         items = [ item ];
  576.                     }
  577.                     rv = Components.results.NS_OK;
  578.                 } else if (item.QueryInterface(calIEvent)) {
  579.                     iid = calIEvent;
  580.                     rv = Components.results.NS_OK;
  581.                     items = [ item ];
  582.                 } else if (item.QueryInterface(calITodo)) {
  583.                     iid = calITodo;
  584.                     rv = Components.results.NS_OK;
  585.                     items = [ item ];
  586.                 } else {
  587.                     errString = "Can't deduce item type based on query";
  588.                     rv = Components.results.NS_ERROR_FAILURE;
  589.                 }
  590.  
  591.             } else { 
  592.                 // XXX
  593.                 debug("aStatusCode = " + aStatusCode + "\n");
  594.                 errString = "XXX";
  595.                 rv = Components.results.NS_ERROR_FAILURE;
  596.             }
  597.  
  598.             // XXX  handle aCount
  599.             if (errString) {
  600.                 debug("errString = " + errString + "\n");
  601.             }
  602.  
  603.             try {
  604.                 aListener.onGetResult(thisCalendar, rv, iid, null,
  605.                                       items ? items.length : 0,
  606.                                       errString ? errString : items);
  607.             } catch (ex) {
  608.                     debug("reportInternal's onGetResult threw an"
  609.                           + " exception " + ex + "; ignoring\n");
  610.             }
  611.             return;
  612.         };
  613.  
  614.         reportListener.onOperationComplete = function(aStatusCode, aResource,
  615.                                                       aOperation, aClosure) {
  616.             
  617.             // XXX test that something reasonable happens w/notfound
  618.  
  619.             // parse aStatusCode
  620.             var rv;
  621.             var errString;
  622.             if (aStatusCode == 200) { // XXX better error checking
  623.                 rv = Components.results.NS_OK;
  624.             } else {
  625.                 rv = Components.results.NS_ERROR_FAILURE;
  626.                 errString = "XXX something bad happened";
  627.             }
  628.  
  629.             // call back the listener
  630.             try {
  631.                 if (aListener) {
  632.                     aListener.onOperationComplete(thisCalendar,
  633.                                                   Components.results.
  634.                                                   NS_ERROR_FAILURE,
  635.                                                   aListener.GET, null,
  636.                                                   errString);
  637.                 }
  638.             } catch (ex) {
  639.                     debug("reportInternal's onOperationComplete threw an"
  640.                           + " exception " + ex + "; ignoring\n");
  641.             }
  642.  
  643.             return;
  644.         };
  645.  
  646.         // convert this into a form the WebDAV service can use
  647.         var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
  648.                       .getService(Components.interfaces.nsIDOMParser);
  649.         queryDoc = xParser.parseFromString(aQuery, "application/xml");
  650.  
  651.         // construct the resource we want to search against
  652.         var calendarDirUri = this.mCalendarUri.clone();
  653.         debug("report uri = " + calendarDirUri.spec + "\n");
  654.         var calendarDirResource = new WebDavResource(calendarDirUri);
  655.  
  656.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  657.             .getService(Components.interfaces.nsIWebDAVService);
  658.         webSvc.report(calendarDirResource, queryDoc, true, reportListener,
  659.                       null);
  660.         return;    
  661.  
  662.     },
  663.  
  664.  
  665.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
  666.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  667.     //                in calIOperationListener aListener );
  668.     getItems: function (aItemFilter, aCount, aRangeStart, aRangeEnd, aListener)
  669.     {
  670.         if (!aListener)
  671.             return;
  672.  
  673.         // this is our basic report xml
  674.         var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
  675.         var D = new Namespace("D", "DAV:");
  676.         default xml namespace = C;
  677.  
  678.         var queryXml = 
  679.           <calendar-query xmlns:D={D}>
  680.             <D:prop>
  681.               <calendar-data/>
  682.             </D:prop>
  683.             <filter>
  684.               <comp-filter name="VCALENDAR">
  685.                 <comp-filter/>
  686.               </comp-filter>
  687.             </filter>
  688.           </calendar-query>;
  689.  
  690.         // figure out 
  691.         var compFilterNames = new Array();
  692.         compFilterNames[calICalendar.ITEM_FILTER_TYPE_TODO] = "VTODO";
  693.         compFilterNames[calICalendar.ITEM_FILTER_TYPE_VJOURNAL] = "VJOURNAL";
  694.         compFilterNames[calICalendar.ITEM_FILTER_TYPE_EVENT] = "VEVENT";
  695.  
  696.         var filterTypes = 0;
  697.         for (var i in compFilterNames) {
  698.             if (aItemFilter & i) {
  699.                 // XXX CalDAV only allows you to ask for one filter-type
  700.                 // at once, so if the caller wants multiple filtern
  701.                 // types, all they're going to get for now is events
  702.                 // (since that's last in the Array).  Sorry!
  703.                 ++filterTypes;
  704.                 queryXml[0].C::filter.C::["comp-filter"]
  705.                     .C::["comp-filter"] = 
  706.                     <comp-filter name={compFilterNames[i]}/>;
  707.             }
  708.         }
  709.  
  710.         if (filterTypes < 1) {
  711.             debug("No item types specified\n");
  712.             // XXX should we just quietly call back the completion method?
  713.             throw NS_ERROR_FAILURE;
  714.         }
  715.  
  716.         // if a time range has been specified, do the appropriate restriction.
  717.         // XXX express "end of time" in caldav by leaving off "start", "end"
  718.         if (aRangeStart && aRangeStart.isValid && 
  719.             aRangeEnd && aRangeEnd.isValid) {
  720.  
  721.             var rangeXml = <time-range start={aRangeStart.icalString}
  722.                                        end={aRangeEnd.icalString}/>;
  723.  
  724.             // append the time-range as a child of our innermost comp-filter
  725.             queryXml[0].C::filter.C::["comp-filter"]
  726.                 .C::["comp-filter"].appendChild(rangeXml);
  727.         }
  728.  
  729.         var queryString = xmlHeader + queryXml.toXMLString();
  730.         debug("getItems(): querying CalDAV server for events: \n" + 
  731.               queryString + "\n");
  732.  
  733.         var occurrences = (aItemFilter &
  734.                            calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0; 
  735.         this.reportInternal(queryString, occurrences, aRangeStart, aRangeEnd,
  736.                             aCount, aListener);
  737.     },
  738.  
  739.     startBatch: function ()
  740.     {
  741.         this.observeBatchChange(true);
  742.     },
  743.     endBatch: function ()
  744.     {
  745.         this.observeBatchChange(false);
  746.     },
  747.  
  748.     //
  749.     // Helper functions
  750.     //
  751.     observeBatchChange: function observeBatchChange (aNewBatchMode) {
  752.         for each (obs in this.mObservers) {
  753.             if (aNewBatchMode) {
  754.                 try {
  755.                     obs.onStartBatch();
  756.                 } catch (ex) {
  757.                     debug("observer's onStartBatch threw an exception " + ex 
  758.                           + "; ignoring\n");
  759.                 }
  760.             } else {
  761.                 try {
  762.                     obs.onEndBatch();
  763.                 } catch (ex) {
  764.                     debug("observer's onEndBatch threw an exception " + ex 
  765.                           + "; ignoring\n");
  766.                 }
  767.             }
  768.         }
  769.         return;
  770.     },
  771.  
  772.     observeAddItem: 
  773.         function observeAddItem(aItem) {
  774.             for each (obs in this.mObservers) {
  775.                 try {
  776.                     obs.onAddItem(aItem);
  777.                 } catch (ex) {
  778.                     debug("observer's onAddItem threw an exception " + ex 
  779.                           + "; ignoring\n");
  780.                 }
  781.             }
  782.             return;
  783.         },
  784.  
  785.     observeModifyItem: 
  786.         function observeModifyItem(aNewItem, aOldItem) {
  787.             for each (obs in this.mObservers) {
  788.                 try {
  789.                     obs.onModifyItem(aNewItem, aOldItem);
  790.                 } catch (ex) {
  791.                     debug("observer's onModifyItem threw an exception " + ex 
  792.                           + "; ignoring\n");
  793.                 }
  794.             }
  795.             return;
  796.         },
  797.  
  798.     observeDeleteItem: 
  799.         function observeDeleteItem(aDeletedItem) {
  800.             for each (obs in this.mObservers) {
  801.                 try {
  802.                     obs.onDeleteItem(aDeletedItem);
  803.                 } catch (ex) {
  804.                     debug("observer's onDeleteItem threw an exception " + ex 
  805.                           + "; ignoring\n");
  806.                 }
  807.             }
  808.             return;
  809.         }
  810. };
  811.  
  812. function WebDavResource(url) {
  813.     this.mResourceURL = url;
  814. }
  815.  
  816. WebDavResource.prototype = {
  817.     mResourceURL: {},
  818.     get resourceURL() { 
  819.         return this.mResourceURL;}  ,
  820.     QueryInterface: function(iid) {
  821.         if (iid.equals(CI.nsIWebDAVResource) ||
  822.             iid.equals(CI.nsISupports)) {
  823.             return this;
  824.         }
  825.        
  826.         throw Components.interfaces.NS_NO_INTERFACE;
  827.     }
  828. };
  829.  
  830. function WebDavListener() {
  831. }
  832.  
  833. WebDavListener.prototype = { 
  834.  
  835.     QueryInterface: function (aIID) {
  836.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  837.             !aIID.equals(nsIWebDavOperationListener)) {
  838.             throw Components.results.NS_ERROR_NO_INTERFACE;
  839.         }
  840.  
  841.         return this;
  842.     },
  843.  
  844.     onOperationComplete: function(aStatusCode, aResource, aOperation,
  845.                                   aClosure) {
  846.         // aClosure is the listener
  847.         aClosure.onOperationComplete(this, aStatusCode, 0, null, null);
  848.  
  849.         debug("WebDavListener.onOperationComplete() called\n");
  850.         return;
  851.     },
  852.  
  853.     onOperationDetail: function(aStatusCode, aResource, aOperation, aDetail,
  854.                                 aClosure) {
  855.         debug("WebDavListener.onOperationDetail() called\n");
  856.         return;
  857.     }
  858. }
  859.  
  860. /****
  861.  **** module registration
  862.  ****/
  863.  
  864. var calDavCalendarModule = {
  865.     mCID: Components.ID("{a35fc6ea-3d92-11d9-89f9-00045ace3b8d}"),
  866.     mContractID: "@mozilla.org/calendar/calendar;1?type=caldav",
  867.     
  868.     registerSelf: function (compMgr, fileSpec, location, type) {
  869.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  870.         compMgr.registerFactoryLocation(this.mCID,
  871.                                         "Calendar CalDAV back-end",
  872.                                         this.mContractID,
  873.                                         fileSpec,
  874.                                         location,
  875.                                         type);
  876.     },
  877.  
  878.     getClassObject: function (compMgr, cid, iid) {
  879.         if (!cid.equals(this.mCID))
  880.             throw Components.results.NS_ERROR_NO_INTERFACE;
  881.  
  882.         if (!iid.equals(Components.interfaces.nsIFactory))
  883.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  884.  
  885.         return this.mFactory;
  886.     },
  887.  
  888.     mFactory: {
  889.         createInstance: function (outer, iid) {
  890.             if (outer != null)
  891.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  892.             return (new calDavCalendar()).QueryInterface(iid);
  893.         }
  894.     },
  895.  
  896.     canUnload: function(compMgr) {
  897.         return true;
  898.     }
  899. };
  900.  
  901.  
  902. function NSGetModule(compMgr, fileSpec) {
  903.     return calDavCalendarModule;
  904. }
  905.