Fix bug 463392 - caldav calendars are not visible in the 'select calendar' dialog. r=philipp
authorSimon Vaillancourt <simon.at.orcl@gmail.com>
Fri, 07 May 2010 14:39:00 +0200
changeset 5609 ef67aa880d3657ac34d10d7d9dc2a3c590314cc9
parent 5608 0f01091253c9a329718852d7d6a8a91d089e6a4b
child 5610 6f195902e307fb410a1c7a95ac70cb36efd3c12a
push id4351
push usermozilla@kewis.ch
push dateSat, 08 May 2010 09:28:37 +0000
treeherdercomm-central@ef67aa880d36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp
bugs463392
Fix bug 463392 - caldav calendars are not visible in the 'select calendar' dialog. r=philipp
calendar/base/content/dialogs/chooseCalendarDialog.xul
calendar/base/modules/calItipUtils.jsm
calendar/base/public/calICalendar.idl
calendar/libical/design-data/parameters.csv
calendar/libical/design-data/params-in-prop.txt
calendar/lightning/content/imip-bar.js
calendar/locales/en-US/chrome/lightning/lightning.properties
calendar/providers/caldav/calDavCalendar.js
calendar/providers/caldav/calDavRequestHandlers.js
--- a/calendar/base/content/dialogs/chooseCalendarDialog.xul
+++ b/calendar/base/content/dialogs/chooseCalendarDialog.xul
@@ -45,17 +45,17 @@
     <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd1;
 ]>
 
 <dialog id="chooseCalendar"
         title="&calendar.select.dialog.title;"
         windowtype="Calendar:CalendarPicker"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         buttons="accept,cancel"
-        onload="loadCalendars();"
+        onload="setTimeout('loadCalendars()',0);"
         ondialogaccept="return doOK();"
         persist="screenX screenY height width">
 
     <script type="application/javascript" src="chrome://calendar/content/calendar-ui-utils.js"/>
     <script type="application/javascript"><![CDATA[
         function loadCalendars() {
             const calendarManager = Components.classes["@mozilla.org/calendar/manager;1"]
                                             .getService(Components.interfaces.calICalendarManager);
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -18,16 +18,17 @@
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Daniel Boelzle <daniel.boelzle@sun.com>
  *   Philipp Kewisch <mozilla@kewis.ch>
  *   Clint Talbert <ctalbert.moz@gmail.com>
  *   Matthew Willis <lilmatt@mozilla.com>
+ *   Simon Vaillancourt <simon.at.orcl@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -562,17 +563,17 @@ function sendMessage(aItem, aMethod, aRe
     aTransport.sendItems(aRecipientsList.length, aRecipientsList, itipItem);
 }
 
 /** local to this module file
  * An operation listener that is used on calendar operations which checks and sends further iTIP
  * messages based on the calendar action.
  *
  * @param opListener operation listener to forward
- * @param oldItem the previous item before modification (if any) 
+ * @param oldItem the previous item before modification (if any)
  */
 function ItipOpListener(opListener, oldItem) {
     this.mOpListener = opListener;
     this.mOldItem = oldItem;
 }
 ItipOpListener.prototype = {
     onOperationComplete: function ItipOpListener_onOperationComplete(aCalendar,
                                                                      aStatus,
@@ -596,16 +597,32 @@ ItipOpListener.prototype = {
                                                      aItemType,
                                                      aDetail,
                                                      aCount,
                                                      aItems) {
     }
 };
 
 /** local to this module file
+ * Add a the parameter SCHEDULE-AGENT=CLIENT to the item before it is
+ * created or updated so that the providers knows scheduling will
+ * be handled by the client.
+ *
+ * @param item item about to be added or updated
+ * @param calendar calendar into which the item is about to be added or updated
+ */
+function addScheduleAgentClient(item, calendar) {
+     if (calendar.getProperty("capabilities.autoschedule.supported") === true) {
+          if (item.organizer) {
+             item.organizer.setProperty("SCHEDULE-AGENT","CLIENT");
+          }
+     }
+}
+
+/** local to this module file
  * An operation listener triggered by cal.itip.processItipItem() for lookup of the sent iTIP item's UID.
  *
  * @param itipItem sent iTIP item
  * @param optionsFunc options func, see cal.itip.processItipItem()
  */
 function ItipFindItemListener(itipItem, optionsFunc) {
     this.mItipItem = itipItem;
     this.mOptionsFunc = optionsFunc;
@@ -692,16 +709,17 @@ ItipFindItemListener.prototype = {
                                         let att = cal.getInvitedAttendee(newItem);
                                         if (!att) { // fall back to using configured organizer
                                             att = createOrganizer(newItem.calendar);
                                             if (att) {
                                                 att.isOrganizer = false;
                                             }
                                         }
                                         if (att) {
+                                            addScheduleAgentClient(newItem, item.calendar);
                                             newItem.removeAttendee(att);
                                             att = att.clone();
                                             let action = function(opListener, partStat) {
                                                 if (!partStat) { // keep PARTSTAT
                                                     let att_ = cal.getInvitedAttendee(item);
                                                     partStat = (att_ ? att_.participationStatus : "NEEDS-ACTION");
                                                 }
                                                 att.participationStatus = partStat;
@@ -793,16 +811,17 @@ ItipFindItemListener.prototype = {
                 switch (method) {
                     case "REQUEST":
                     case "PUBLISH": {
                         let this_ = this;
                         let action = function(opListener, partStat) {
                             let newItem = itipItemItem.clone();
                             setReceivedInfo(newItem, itipItemItem);
                             newItem.parentItem.calendar = this_.mItipItem.targetCalendar;
+                            addScheduleAgentClient(newItem, this_.mItipItem.targetCalendar);
                             if (partStat) {
                                 if (partStat != "DECLINED") {
                                     cal.alarms.setDefaultValues(newItem);
                                 }
                                 let att = cal.getInvitedAttendee(newItem);
                                 if (!att) { // fall back to using configured organizer
                                     att = createOrganizer(newItem.calendar);
                                     if (att) {
--- a/calendar/base/public/calICalendar.idl
+++ b/calendar/base/public/calICalendar.idl
@@ -183,16 +183,17 @@ interface calICalendar : nsISupports
    *   capabilities.privacy.supported      Supports a privacy state
    *   capabilities.priority.supported     Supports the priority field
    *   capabilities.events.supported       Supports tasks
    *   capabilities.tasks.supported        Supports events
    *   capabilities.alarms.popup.supported Supports popup alarms
    *   capabilities.alarms.oninviations.supported Supports alarms on inviations.
    *   capabilities.timezones.floating.supported Supports local time
    *   capabilities.timezones.UTC.supported      Supports UTC/GMT timezone
+   *   capabilities.autoschedule.supported       Supports caldav schedule properties in icalendar (SCHEDULE-AGENT, SCHEDULE-STATUS...)
    *
    * The following capabilities are used to restrict the values for specific
    * fields. An array should be specified with the values, the default
    * values are specified here. Extensions using this need to take care of
    * adding any UI elements needed in an overlay. To make sure the correct
    * elements are shown, those elements should additionally specify an attribute
    * "provider", with the type of the provider.
    *
--- a/calendar/libical/design-data/parameters.csv
+++ b/calendar/libical/design-data/parameters.csv
@@ -29,8 +29,11 @@
 "#this parameter should really be called ACTION, but this conflicts with the ACTION property"
 "ACTIONPARAM","icalparameter_action","ASK;ABORT"
 "ID","const char*",
 "ENABLE","icalparameter_enable","TRUE;FALSE"
 "LATENCY","const char*",
 "LOCAL","icalparameter_local","TRUE;FALSE"
 "LOCALIZE","const char*",
 "OPTIONS","const char*",
+"SCHEDULE-AGENT","const char*",
+"SCHEDULE-STATUS","const char*",
+"SCHEDULE-FORCE-SEND","const char*",
--- a/calendar/libical/design-data/params-in-prop.txt
+++ b/calendar/libical/design-data/params-in-prop.txt
@@ -1,11 +1,11 @@
 ACTION               VALUE X
 ATTACH               FMTTYPE ENCODING VALUE X
-ATTENDEE             CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY RECEIVED-SEQUENCE RECEIVED-DTSTAMP X
+ATTENDEE             CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY RECEIVED-SEQUENCE RECEIVED-DTSTAMP SCHEDULE-STATUS SCHEDULE-AGENT SCHEDULE-FORCE-SEND X
 CALSCALE             X
 CATEGORIES           LANGUAGE X
 CLASS                X
 CMD		     ACTIONPARAM ID LATENCY LOCALIZE OPTIONS X
 COMMENT              ALTREP LANGUAGE X
 COMPLETED            X
 CONTACT              ALTREP LANGUAGE X
 CREATED              X
@@ -17,17 +17,17 @@ DUE                  VALUE TZID X
 DURATION             X
 EXDATE               VALUE TZID X
 EXRULE               X
 FREEBUSY             FBTYPE X
 GEO                  X
 LAST-MODIFIED        X
 LOCATION             ALTREP LANGUAGE X
 METHOD               X
-ORGANIZER            CN DIR LANGUAGE SENT-BY X
+ORGANIZER            CN DIR LANGUAGE SENT-BY SCHEDULE-STATUS SCHEDULE-AGENT SCHEDULE-FORCE-SEND X
 PERCENT-COMPLETE     X
 PRIORITY             X
 PRODID               X
 RDATE                VALUE TZID X
 RECURRENCE-ID        VALUE RANGE TZID X
 RELATED-TO           RELTYPE X
 REPEAT               X
 REQUEST-STATUS       LANGUAGE X
--- a/calendar/lightning/content/imip-bar.js
+++ b/calendar/lightning/content/imip-bar.js
@@ -18,16 +18,17 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Clint Talbert <ctalbert.moz@gmail.com>
  *   Matthew Willis <lilmatt@mozilla.com>
  *   Philipp Kewisch <mozilla@kewis.ch>
  *   Daniel Boelzle <daniel.boelzle@sun.com>
  *   Martin Schroeder <mschroeder@mozilla.x-home.org>
+ *   Simon Vaillancourt <simon.at.orcl@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -374,32 +375,35 @@ function ltnItipOptions(itipItem, rc, ac
     } else {
         imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
     }
 }
 
 function ltnGetTargetCalendar(itipItem) {
     let calendarToReturn = null;
     let calendars = getCalendarManager().getCalendars({}).filter(ltnIsSchedulingCalendar);
-    // XXXNeed an error message if there is no calendar
 
     if (itipItem.receivedMethod == "REQUEST") {
         // try to further limit down the list to those calendars that are configured to a matching attendee;
         let item = itipItem.getItemList({})[0];
         let matchingCals = calendars.filter(
             function(calendar) {
                 return (cal.getInvitedAttendee(item, calendar) != null);
             });
         // if there's none, we will show the whole list of calendars:
         if (matchingCals.length > 0) {
             calendars = matchingCals;
         }
     }
 
-    if (calendars.length == 1) {
+    if (calendars.length == 0) {
+        var stringBundle = getLightningStringBundle();
+        window.alert(stringBundle.GetStringFromName("imipNoCalendarAvailable"));
+    }
+    else if (calendars.length == 1) {
         // There's only one calendar, so it's silly to ask what calendar
         // the user wants to import into.
         calendarToReturn = calendars[0];
     } else {
         // Ask what calendar to import into
         var args = {};
         args.calendars = calendars;
         args.onOk = function selectCalendar(aCal) { calendarToReturn = aCal; };
--- a/calendar/locales/en-US/chrome/lightning/lightning.properties
+++ b/calendar/locales/en-US/chrome/lightning/lightning.properties
@@ -79,16 +79,17 @@ imipCancelInvitation.label=Delete
 imipDeclineInvitation.label=Decline
 imipUpdate.label=Update
 imipAcceptTentativeInvitation.label=Tentative
 imipSend.label=Send
 imipSendMail.title=E-Mail Notification
 imipSendMail.text=Would you like to send out notification E-Mail now?
 imipSendMail.Outlook2000CompatMode.text=Support Outlook 2000 and Outlook 2002/XP
 imipNoIdentity=None
+imipNoCalendarAvailable=There are no writable calendars available.
 
 itipReplySubject=Event Invitation Reply: %1$S
 itipReplyBodyAccept=%1$S has accepted your event invitation.
 itipReplyBodyDecline=%1$S has declined your event invitation.
 itipRequestSubject=Event Invitation: %1$S
 itipRequestBody=%1$S has invited you to %2$S
 itipCancelSubject=Event Canceled: %1$S
 itipCancelBody=%1$S has canceled this event: « %2$S »
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -380,26 +380,26 @@ calDavCalendar.prototype = {
                     return this.calendarUserAddress;
                 } // else use configured email identity
                 break;
             case "organizerCN":
                 return null; // xxx todo
             case "cache.updateTimer":
                 return getPrefSafe("calendar.autorefresh.timeout");
             case "itip.transport":
-                if (this.hasAutoScheduling) {
-                    return null;
-                } else if (this.hasScheduling) {
+                if (this.hasAutoScheduling || this.hasScheduling) {
                     return this.QueryInterface(Components.interfaces.calIItipTransport);
                 } // else use outbound email-based iTIP (from cal.ProviderBase)
                 break;
             case "capabilities.tasks.supported":
                 return (this.supportedItemTypes.indexOf("VTODO") > -1);
             case "capabilities.events.supported":
                 return (this.supportedItemTypes.indexOf("VEVENT") > -1);
+            case "capabilities.autoschedule.supported":
+                return this.hasAutoScheduling;
         }
         return this.__proto__.__proto__.getProperty.apply(this, arguments);
     },
 
     promptOverwrite: function caldav_promptOverwrite(aMethod, aItem, aListener, aOldItem) {
         var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                       .getService(Components.interfaces.nsIPromptService);
 
@@ -714,17 +714,18 @@ calDavCalendar.prototype = {
                     if (thisCalendar.isCached) {
                         // the item is deleted in the storage calendar from calCachedCalendar
                         realListener.onOperationComplete(thisCalendar, status,
                                                          Components.interfaces.calIOperationListener.DELETE,
                                                          null, null);
                     } else {
                         thisCalendar.mTargetCalendar.deleteItem(aItem, aListener);
                     }
-                    delete thisCalendar.mHrefIndex[eventUri.path];
+                    let decodedHRef = decodeURIComponent(eventUri.path);
+                    delete thisCalendar.mHrefIndex[decodedHRef];
                     delete thisCalendar.mItemInfoCache[aItem.id];
                     cal.LOG("CalDAV: Item deleted successfully from calendar" +
                             thisCalendar.name);
                 }
             } else if (status == 412) {
                 // item has either been modified or deleted by someone else
                 // check to see which
 
@@ -2145,17 +2146,23 @@ calDavCalendar.prototype = {
                 thisCalendar.doDeleteItem(aItem, null, true, true, delUri);
             }
         };
 
         this.mTargetCalendar.getItem(aItem.id, getItemListener);
     },
 
     canNotify: function caldav_canNotify(aMethod, aItem) {
-        if (this.hasAutoScheduling) { // auto-sched takes precedence
+        if (this.hasAutoScheduling) {
+            // canNotify should return false if the schedule agent is client
+            // so the itip transport(imip) takes care of notifying participants
+            if (aItem.organizer &&
+                aItem.organizer.getProperty("SCHEDULE-AGENT") == "CLIENT") {
+                return false;
+            }
             return true;
         }
         return false; // use outbound iTIP for all
     },
 
     //
     // calIItipTransport interface
     //
@@ -2169,16 +2176,36 @@ calDavCalendar.prototype = {
         return this.mSenderAddress || this.calendarUserAddress;
     },
     set senderAddress(aString) {
         return (this.mSenderAddress = aString);
     },
 
     sendItems: function caldav_sendItems(aCount, aRecipients, aItipItem) {
 
+        if (this.hasAutoScheduling) {
+            // If auto scheduling is supported by the server we still need
+            // to send out REPLIES for meetings where the ORGANIZER has the
+            // parameter SCHEDULE-AGENT set to CLIENT, this property is
+            // checked in in canNotify()
+            if (aItipItem.responseMethod == "REPLY") {
+                let imipTransport = cal.getImipTransport(this);
+                if (imipTransport) {
+                    imipTransport.sendItems(aCount, aRecipients, aItipItem);
+                }
+            }
+            // Servers supporting auto schedule should handle all other
+            // scheduling operations for now. Note that eventually the client
+            // could support setting a SCHEDULE-AGENT=CLIENT parameter on
+            // ATTENDEES and/or interpreting the SCHEDULE-STATUS parameter which
+            // could translate in the client sending out IMIP REQUESTS
+            // for specific attendees.
+            return;
+        }
+
         if (aItipItem.responseMethod == "REPLY") {
             // Get my participation status
             var attendee = aItipItem.getItemList({})[0].getAttendeeById(this.calendarUserAddress);
             if (!attendee) {
                 return;
             }
             // work around BUG 351589, the below just removes RSVP:
             aItipItem.setAttendeeStatus(attendee.id, attendee.participationStatus);
--- a/calendar/providers/caldav/calDavRequestHandlers.js
+++ b/calendar/providers/caldav/calDavRequestHandlers.js
@@ -734,17 +734,17 @@ multigetSyncHandler.prototype = {
         let D = new Namespace("D", "DAV:");
         let queryXml =
           <calendar-multiget xmlns:D={D} xmlns={C}>
             <D:prop>
               <D:getetag/>
               <calendar-data/>
             </D:prop>
           </calendar-multiget>;
-          
+
         let batchSize = cal.getPrefSafe("calendar.caldav.multigetBatchSize", 100);
         while (this.itemsNeedFetching.length && batchSize > 0) {
             batchSize --;
             let locpath = this.itemsNeedFetching.pop();
             queryXml.D::prop += <D:href xmlns:D={D}>{locpath}</D:href>;
         }
 
         let multigetQueryString = xmlHeader + queryXml.toXMLString();
@@ -799,17 +799,17 @@ multigetSyncHandler.prototype = {
             return;
         }
         if (this.itemsNeedFetching.length == 0) {
             if (this.newSyncToken) {
                 this.calendar.mWebdavSyncToken = this.newSyncToken;
                 this.calendar.mTargetCalendar.setMetaData("sync-token", this.newSyncToken);
               cal.LOG("CalDAV: New webdav-sync Token: " + this.calendar.mWebdavSyncToken);
             }
-    
+
             this.calendar.finalizeUpdatedItems(this.changelogListener,
                                                this.baseUri);
         }
         if (!this._reader) {
             // No reader means there was a request error
             cal.LOG("CalDAV: onStopRequest: no reader");
             return;
         }
@@ -819,17 +819,17 @@ multigetSyncHandler.prototype = {
             this._reader = null;
         }
         if (this.itemsNeedFetching.length > 0) {
             cal.LOG("CalDAV: Still need to fetch " + this.itemsNeedFetching.length + " elements.");
             this._reader = Components.classes["@mozilla.org/saxparser/xmlreader;1"]
                                      .createInstance(Components.interfaces.nsISAXXMLReader);
             this._reader.contentHandler = this;
             this._reader.errorHandler = this;
-            this._reader.parseAsync(null);            
+            this._reader.parseAsync(null);
             let timerCallback = {
                 requestHandler : this,
                 notify: function(timer) {
                     // Call multiget again to get another batch
                     this.requestHandler.doMultiGet();
                 }
             };
             let timer = Components.classes["@mozilla.org/timer;1"]
@@ -872,17 +872,17 @@ multigetSyncHandler.prototype = {
         if (this.calendar.isCached) {
             this.calendar.superCalendar.startBatch();
         }
     },
 
     endDocument: function mg_endDocument() {
         if (this.calendar.isCached) {
             this.calendar.superCalendar.endBatch();
-        }        
+        }
     },
 
     startElement: function mg_startElement(aUri, aLocalName, aQName, aAttributes) {
         switch (aLocalName) {
             case "response":
                 this.currentResponse = {};
                 this.tag = null
                 this.isInPropStat=false;
@@ -943,17 +943,17 @@ multigetSyncHandler.prototype = {
                         oldEtag = null;
                     }
                     if (!oldEtag || oldEtag != r.getetag) {
                         this.changeCount++;
                         this.calendar.addTargetCalendarItem(r.href,
                                                             r.calendardata,
                                                             this.baseUri,
                                                             r.getetag,
-                                                            null);
+                                                            this.listener);
                     }
                 } else {
                     cal.WARN("CalDAV: Unexpected response, status: " +
                              r.status + ", href: " + r.href + " calendar-data:");
                     this.unhandledErrors++;
                 }
                 break;
             case "propstat":