home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / sunbird / js / calWcapCalendarItems.js < prev    next >
Encoding:
JavaScript  |  2007-05-23  |  58.4 KB  |  1,466 lines

  1. /* -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public
  6.  * License Version 1.1 (the "License"); you may not use this file
  7.  * except in compliance with the License. You may obtain a copy of
  8.  * the License at http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS
  11.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  12.  * implied. See the License for the specific language governing
  13.  * rights and limitations under the License.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  17.  * The Initial Developer of the Original Code is Sun Microsystems, Inc.
  18.  * Portions created by Sun Microsystems are Copyright (C) 2006 Sun
  19.  * Microsystems, Inc. All Rights Reserved.
  20.  *
  21.  * Original Author: Daniel Boelzle (daniel.boelzle@sun.com)
  22.  *
  23.  * Contributor(s):
  24.  *
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the NPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the NPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. var calWcapCalendar;
  41. if (!calWcapCalendar) {
  42.     calWcapCalendar = {};
  43.     calWcapCalendar.prototype = {};
  44. }
  45.  
  46. calWcapCalendar.prototype.encodeAttendee =
  47. function calWcapCalendar_encodeAttendee(att)
  48. {
  49.     if (LOG_LEVEL > 2)
  50.         log("attendee.icalProperty.icalString=" + att.icalProperty.icalString, this);
  51.     function encodeAttr(val, attr, params) {
  52.         if (val && val.length > 0) {
  53.             if (params.length > 0)
  54.                 params += "^";
  55.             if (attr)
  56.                 params += (attr + "=");
  57.             params += encodeURIComponent(val);
  58.         }
  59.         return params;
  60.     }
  61.     var params = encodeAttr(att.rsvp ? "TRUE" : "FALSE", "RSVP", "");
  62.     params = encodeAttr(att.participationStatus, "PARTSTAT", params);
  63.     params = encodeAttr(att.role, "ROLE", params);
  64.     params = encodeAttr(att.commonName, "CN", params);
  65.     return encodeAttr(att.id, null, params);
  66. };
  67.  
  68. calWcapCalendar.prototype.getRecurrenceParams =
  69. function calWcapCalendar_getRecurrenceParams(
  70.     item, out_rrules, out_rdates, out_exrules, out_exdates)
  71. {
  72.     // recurrences:
  73.     out_rrules.value = [];
  74.     out_rdates.value = [];
  75.     out_exrules.value = [];
  76.     out_exdates.value = [];
  77.     if (item.recurrenceInfo) {
  78.         var rItems = item.recurrenceInfo.getRecurrenceItems({});
  79.         for each (var rItem in rItems) {
  80.             var isNeg = rItem.isNegative;
  81.             if (rItem instanceof Components.interfaces.calIRecurrenceRule) {
  82.                 var rule = ("\"" + encodeURIComponent(
  83.                                 rItem.icalProperty.valueAsIcalString) +
  84.                             "\"");
  85.                 if (isNeg)
  86.                     out_exrules.value.push(rule);
  87.                 else
  88.                     out_rrules.value.push(rule);
  89.             }
  90.             else if (rItem instanceof Components.interfaces.calIRecurrenceDateSet) {
  91.                 var d = rItem.getDates({});
  92.                 for each (var d in rdates) {
  93.                     if (isNeg)
  94.                         out_exdates.value.push( getIcalUTC(d.date) );
  95.                     else
  96.                         out_rdates.value.push( getIcalUTC(d.date) );
  97.                 }
  98.             }
  99.             else if (rItem instanceof Components.interfaces.calIRecurrenceDate) {
  100.                 if (isNeg)
  101.                     out_exdates.value.push( getIcalUTC(rItem.date) );
  102.                 else
  103.                     out_rdates.value.push( getIcalUTC(rItem.date) );
  104.             }
  105.             else {
  106.                 this.notifyError("don\'t know how to handle this recurrence item: " +
  107.                                  rItem.valueAsIcalString);
  108.             }
  109.         }
  110.     }
  111. };
  112.  
  113. calWcapCalendar.prototype.encodeRecurrenceParams =
  114. function calWcapCalendar_encodeRecurrenceParams(item, oldItem)
  115. {
  116.     var rrules = {};
  117.     var rdates = {};
  118.     var exrules = {};
  119.     var exdates = {};
  120.     this.getRecurrenceParams(item, rrules, rdates, exrules, exdates);
  121.     if (oldItem) {
  122.         // actually only write changes if an old item has been changed, because
  123.         // cs recreates the whole series if a rule has changed.
  124.         // xxx todo: one problem is left when a master only holds EXDATEs,
  125.         //           and effectively no item is present anymore.
  126.         //           cs seems not to clean up automatically, but it does when
  127.         //           when deleting an occurrence {id, rec-id}!
  128.         //           So this still leaves the question open why deleteOccurrence
  129.         //           does not directly call deleteItem rather than modifyItem,
  130.         //           which leads to a much cleaner usage.
  131.         //           I assume this mimic has been chosen for easier undo/redo
  132.         //           support (Undo would then have to distinguish whether
  133.         //           it has previously deleted an occurrence or ordinary item:
  134.         //            - entering an exception again
  135.         //            - or adding an item)
  136.         //           Currently it can just modifyItem(newItem/oldItem) back.
  137.         var rrules_ = {};
  138.         var rdates_ = {};
  139.         var exrules_ = {};
  140.         var exdates_ = {};
  141.         this.getRecurrenceParams(oldItem, rrules_, rdates_, exrules_, exdates_);
  142.         
  143.         function sameSet(list, list_) {
  144.             return (list.length == list_.length &&
  145.                     list.every( function everyFunc(x) {
  146.                                     return list_.some(
  147.                                         function someFunc(y) { return x == y; } );
  148.                                 }
  149.                         ));
  150.         }
  151.         if (sameSet(rrules.value, rrules_.value))
  152.             rrules.value = null; // don't write
  153.         if (sameSet(rdates.value, rdates_.value))
  154.             rdates.value = null; // don't write
  155.         if (sameSet(exrules.value, exrules.value))
  156.             exrules.value = null; // don't write
  157.         if (sameSet(exdates.value, exdates_.value))
  158.             exdates.value = null; // don't write
  159.     }
  160.     
  161.     function encodeList(list) {
  162.         var ret = "";
  163.         for each ( var str in list ) {
  164.             if (ret.length > 0)
  165.                 ret += ";";
  166.             ret += str;
  167.         }
  168.         return ret;
  169.     }
  170.     var ret = "";
  171.     if (rrules.value)
  172.         ret += ("&rrules=" + encodeList(rrules.value));
  173.     if (rdates.value)
  174.         ret += ("&rdates=" + encodeList(rdates.value));
  175.     if (exrules.value)
  176.         ret += ("&exrules=" + encodeList(exrules.value));
  177.     if (exdates.value)
  178.         ret += ("&exdates=" + encodeList(exdates.value));
  179.     return ret;
  180.     // xxx todo:
  181.     // rchange=1: expand recurrences,
  182.     // or whether to replace the rrule, ambiguous documentation!!!
  183.     // check with store(with no uid) upon adoptItem() which behaves strange
  184.     // if rchange=0 is set!
  185. };
  186.  
  187. calWcapCalendar.prototype.getAlarmParams =
  188. function calWcapCalendar_getAlarmParams(item)
  189. {
  190.     var params = null;
  191.     var alarmStart = item.alarmOffset;
  192.     if (alarmStart) {
  193.         if (item.alarmRelated == calIItemBase.ALARM_RELATED_END) {
  194.             // cs does not support explicit RELATED=END when
  195.             // both start|entry and end|due are written
  196.             var dur = item.duration;
  197.             if (dur) { // both given
  198.                 alarmStart = alarmStart.clone();
  199.                 alarmStart.addDuration(dur);
  200.             } // else only end|due is set, alarm makes little sense though
  201.         }
  202.         
  203.         var emails = "";
  204.         if (item.hasProperty("alarmEmailAddress"))
  205.             emails = encodeURIComponent(item.getProperty("alarmEmailAddress"));
  206.         else {
  207.             this.session.getDefaultAlarmEmails({}).forEach(
  208.                 function forEachFunc(email) {
  209.                     if (emails.length > 0)
  210.                         emails += ";";
  211.                     emails += encodeURIComponent(email);
  212.                 });
  213.         }
  214.         if (emails.length > 0) {
  215.             params = ("&alarmStart=" + alarmStart.icalString);
  216.             params += ("&alarmEmails=" + emails);
  217.         }
  218.         // else popup
  219.     }
  220.     if (!params) // clear popup, email alarm:
  221.         params = "&alarmStart=&alarmPopup=&alarmEmails=";
  222.     return params;
  223. };
  224.  
  225. // why ever, X-S1CS-EMAIL is unsupported though documented
  226. // for get_calprops... WTF.
  227. function getCalId(att) {
  228.     return (att ? att.getProperty("X-S1CS-CALID") : null);
  229. }
  230.  
  231. function getAttendeeByCalId(atts, calId) {
  232.     for each (var att in atts) {
  233.         if (getCalId(att) == calId)
  234.             return att;
  235.     }
  236.     return null;
  237. }
  238.  
  239. calWcapCalendar.prototype.isInvitation =
  240. function calWcapCalendar_isInvitation(item)
  241. {
  242.     if (!this.session.isLoggedIn)
  243.         return false; // don't know
  244.     var orgCalId = getCalId(item.organizer);
  245.     if (!orgCalId)
  246.         return false;
  247.     var calId = this.calId;
  248.     if (orgCalId == calId)
  249.         return false;
  250.     return (getAttendeeByCalId(item.getAttendees({}), calId) != null);
  251. };
  252.  
  253. calWcapCalendar.prototype.getInvitedAttendee =
  254. function calWcapCalendar_getInvitedAttendee(item)
  255. {
  256.     return getAttendeeByCalId(item.getAttendees({}), this.calId);
  257. };
  258.  
  259. function equalDatetimes(one, two) {
  260.     return ((!one && !two) ||
  261.             (one && two && (one.compare(two) == 0)));
  262. }
  263.  
  264. function identicalDatetimes(one, two) {
  265.     return ((!one && !two) ||
  266.             (one && two && (one.compare(two) == 0) && (one.timezone == two.timezone)));
  267. }
  268.  
  269. // @return null if nothing has changed else value to be written
  270. function diffProperty(newItem, oldItem, propName) {
  271.     var val = newItem.getProperty(propName);
  272.     var oldVal = (oldItem ? oldItem.getProperty(propName) : null);
  273.     if (val === null) {
  274.         // force being set when - no old item, eg when adding new item
  275.         //                      - property is to be deleted
  276.         if (!oldItem || oldVal)
  277.             val = "";
  278.     }
  279.     else {
  280.         val = val.replace(/(\r\n)|\n/g, "\r\n");
  281.         if (oldVal)
  282.             oldVal = oldVal.replace(/(\r\n)|\n/g, "\r\n");
  283.         if (val == oldVal)
  284.             val = null;
  285.     }
  286.     return val;
  287. }
  288.  
  289. calWcapCalendar.prototype.storeItem =
  290. function calWcapCalendar_storeItem(bAddItem, item, oldItem, request, netRespFunc)
  291. {
  292.     var this_ = this;
  293.     var bIsEvent = isEvent(item);
  294.     var bIsParent = isParent(item);
  295.     
  296.     var bAttendeeReply = false;
  297.     var bOrgRequest = false;
  298.     var params = "";
  299.     
  300.     var calId = this.calId;
  301.     if (this.isInvitation(item)) { // REPLY
  302.         bAttendeeReply = true;
  303.         var att = getAttendeeByCalId(item.getAttendees({}), calId);
  304.         if (att) {
  305.             log("attendee: " + att.icalProperty.icalString, this);
  306.             var oldAtt = null;
  307.             if (oldItem)
  308.                 oldAtt = getAttendeeByCalId(oldItem.getAttendees({}), calId);
  309.             if (!oldAtt || (att.participationStatus != oldAtt.participationStatus)) {
  310.                 // REPLY first for just this calendar:
  311.                 params += ("&attendees=PARTSTAT=" + att.participationStatus +
  312.                            "^" + encodeURIComponent(att.id));
  313.             }
  314.         }
  315.     }
  316.     else { // PUBLISH, REQUEST
  317.         
  318.         // workarounds for server bugs concerning recurrences/exceptions:
  319.         // - if first occurrence is an exception
  320.         //   and an EXDATE for that occurrence ought to be written,
  321.         //   then the master item's data is replaced with that EXDATEd exception. WTF.
  322.         // - if start/end date is being written on master, the previously EXDATEd
  323.         //   exception overwrites master, why ever.
  324.         // So in these cases: write all data of master.
  325.         
  326.         var bIsAllDay = false;
  327.         if (bIsEvent) {
  328.             var dtstart = item.startDate;
  329.             var dtend = item.endDate;
  330.             bIsAllDay = (dtstart.isDate && dtend.isDate);
  331.             if (!oldItem || !identicalDatetimes(dtstart, oldItem.startDate)
  332.                          || !identicalDatetimes(dtend, oldItem.endDate)) {
  333.                 params += ("&dtstart=" + getIcalUTC(dtstart));
  334.                 params += ("&X-NSCP-DTSTART-TZID=" +
  335.                            "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + 
  336.                            encodeURIComponent(this.getAlignedTimezone(dtstart.timezone)));
  337.                 params += ("&dtend=" + getIcalUTC(dtend));
  338.                 params += ("&X-NSCP-DTEND-TZID=" +
  339.                            "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + 
  340.                            encodeURIComponent(this.getAlignedTimezone(dtend.timezone)));
  341.                 params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0");
  342.                 
  343.                 if (bIsParent && item.recurrenceInfo)
  344.                     oldItem = null; // recurrence/exceptions hack: write whole master
  345.             }
  346.         }
  347.         else { // calITodo
  348.             // xxx todo: dtstart is mandatory for cs, so if this is
  349.             //           undefined, assume an allDay todo???
  350.             var dtstart = item.entryDate;
  351.             var dtend = item.dueDate;
  352.             bIsAllDay = (dtstart && dtstart.isDate);
  353.             if (!oldItem || !identicalDatetimes(dtstart, oldItem.entryDate)
  354.                          || !identicalDatetimes(dtend, oldItem.dueDate)) {
  355.                 params += ("&dtstart=" + getIcalUTC(dtstart));
  356.                 if (dtstart) {
  357.                     params += ("&X-NSCP-DTSTART-TZID=" +
  358.                                "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + 
  359.                                encodeURIComponent(this.getAlignedTimezone(dtstart.timezone)));
  360.                 }
  361.                 params += ("&due=" + getIcalUTC(dtend));
  362.                 if (dtend) {
  363.                     params += ("&X-NSCP-DUE-TZID=" +
  364.                                "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + 
  365.                                encodeURIComponent(this.getAlignedTimezone(dtend.timezone)));
  366.                 }
  367.                 params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0");
  368.                 
  369.                 if (bIsParent && item.recurrenceInfo)
  370.                     oldItem = null; // recurrence/exceptions hack: write whole master
  371.             }
  372.         }
  373.         if (bIsParent) {
  374.             var recParams = this.encodeRecurrenceParams(item, oldItem);
  375.             if (recParams.length > 0) {
  376.                 oldItem = null; // recurrence/exceptions hack: write whole master
  377.                 params += recParams;
  378.             }
  379.         }
  380.         
  381.         var attendees = item.getAttendees({});
  382.         if (attendees.length > 0) {
  383.             // xxx todo: why ever, X-S1CS-EMAIL is unsupported though documented
  384.             //           for calprops... WTF.
  385.             bOrgRequest = true;
  386.             function encodeAttendees(atts) {
  387.                 function stringSort(one, two) {
  388.                     if (one == two)
  389.                         return 0;
  390.                     return (one < two ? -1 : 1);
  391.                 }
  392.                 atts = atts.concat([]);
  393.                 atts.sort(stringSort);
  394.                 var ret = "";
  395.                 for (var i = 0; i < atts.length; ++i) {
  396.                     if (ret.length > 0)
  397.                         ret += ";";
  398.                     ret += this_.encodeAttendee(atts[i]);
  399.                 }
  400.                 return ret;
  401.             }
  402.             var attParam = encodeAttendees(attendees);
  403.             if (!oldItem || attParam != encodeAttendees(oldItem.getAttendees({}))) {
  404.                 params += ("&orgCalid=" + encodeURIComponent(calId));
  405.                 params += ("&attendees=" + attParam);
  406.             }
  407.         }
  408.         // else using just PUBLISH (method=1)
  409.         else if (oldItem && oldItem.getAttendees({}).length > 0) {
  410.             params += "&attendees="; // clear attendees
  411.         }
  412.         
  413.         var val = item.title;
  414.         if (!oldItem || val != oldItem.title)
  415.             params += ("&summary=" + encodeURIComponent(val));
  416.         // xxx todo: missing relatedTos= in cal api
  417.         val = diffProperty(item, oldItem, "CATEGORIES");
  418.         if (val !== null) // xxx todo: check whether ;-separated:
  419.             params += ("&categories=" + encodeURIComponent( val.replace(/,/g, ";") ));
  420.         val = diffProperty(item, oldItem, "DESCRIPTION");
  421.         if (val !== null)
  422.             params += ("&desc=" + encodeURIComponent(val));
  423.         val = diffProperty(item, oldItem, "LOCATION");
  424.         if (val !== null)
  425.             params += ("&location=" + encodeURIComponent(val));
  426.         val = diffProperty(item, oldItem, "URL");
  427.         if (val !== null)
  428.             params += ("&icsUrl=" + encodeURIComponent(val));
  429.         // xxx todo: default prio is 0 (5 in sjs cs)
  430.         val = item.priority;
  431.         if (!oldItem || val != oldItem.priority)
  432.             params += ("&priority=" + encodeURIComponent(val));
  433.         
  434.         function getPrivacy(item) {
  435.             return ((item.privacy && item.privacy != "") ? item.privacy : "PUBLIC");
  436.         }
  437.         var icsClass = getPrivacy(item);
  438.         if (!oldItem || icsClass != getPrivacy(oldItem))
  439.             params += ("&icsClass="+ icsClass);
  440.         
  441.         if (!oldItem || item.status != oldItem.status) {
  442.             switch (item.status) {
  443.             case "CONFIRMED":    params += "&status=0"; break;
  444.             case "CANCELLED":    params += "&status=1"; break;
  445.             case "TENTATIVE":    params += "&status=2"; break;
  446.             case "NEEDS-ACTION": params += "&status=3"; break;
  447.             case "COMPLETED":    params += "&status=4"; break;
  448.             case "IN-PROCESS":   params += "&status=5"; break;
  449.             case "DRAFT":        params += "&status=6"; break;
  450.             case "FINAL":        params += "&status=7"; break;
  451.             default:
  452.                 params += "&status=3"; // NEEDS-ACTION
  453.                 break;
  454.             }
  455.         }
  456.         
  457.         val = diffProperty(item, oldItem, "TRANSP");
  458.         if (val !== null) {
  459.             switch (val) {
  460.             case "TRANSPARENT":
  461.                 params += "&transparent=1";
  462.                 break;
  463.             case "OPAQUE":
  464.                 params += "&transparent=0";
  465.                 break;
  466.             default:
  467.                 params += ("&transparent=" +
  468.                            (((icsClass == "PRIVATE") || bIsAllDay) ? "1" : "0"));
  469.                 break;
  470.             }
  471.         }
  472.         
  473.         if (!bIsEvent) {
  474.             if (!oldItem || item.percentComplete != oldItem.percentComplete)
  475.                 params += ("&percent=" + item.percentComplete.toString(10));
  476.             if (!oldItem || !equalDatetimes(item.completedDate, oldItem.completedDate))
  477.                 params += ("&completed=" + getIcalUTC(item.completedDate));
  478.         }
  479.         
  480.         // attachment urls:
  481.         function getAttachments(item) {
  482.             var ret = "";
  483.             var attachments = item.attachments;
  484.             if (attachments) {
  485.                 var strings = [];
  486.                 for each (var att in attachements) {
  487.                     if (typeof(att) == "string")
  488.                         strings.push(encodeURIComponent(att));
  489.                     else // xxx todo
  490.                         logError("only URLs supported as attachment, not: " + att, this_);
  491.                 }
  492.                 strings.sort();
  493.                 for (var i = 0; i < strings.length; ++i) {
  494.                     if (i > 0)
  495.                         ret += ";";
  496.                     ret += strings[i];
  497.                 }
  498.             }
  499.             return ret;
  500.         }
  501.         var val = getAttachments(item);
  502.         if (!oldItem || val != getAttachments(oldItem))
  503.             params += ("&attachments=" + val);        
  504.     } // PUBLISH, REQUEST
  505.     
  506.     var alarmParams = this.getAlarmParams(item);
  507.     if (!oldItem || (this.getAlarmParams(oldItem) != alarmParams)) {
  508.         if (bOrgRequest && params.length == 0) {
  509.             // assure no email notifications about this change:
  510.             params += "&smtp=0&smtpNotify=0";
  511.         }
  512.         params += alarmParams;
  513.     }
  514.     
  515.     if (params.length == 0) {
  516.         log("no change at all.", this);
  517.         if (LOG_LEVEL > 2) {
  518.             log("old item:\n" + oldItem.icalString + "\n\nnew item:\n" + item.icalString, this);
  519.         }
  520.         request.execRespFunc(null, item);
  521.     }
  522.     else {
  523.         var someDate = (item.startDate || item.entryDate || item.dueDate);
  524.         if (someDate) {
  525.             // provide some date: eMail notification dates are influenced by this parameter...
  526.             params += ("&tzid=" + encodeURIComponent(
  527.                            this.getAlignedTimezone(someDate.timezone)));
  528.         }
  529.         
  530.         if (item.id)
  531.             params += ("&uid=" + encodeURIComponent(item.id));
  532.         
  533.         // be picky about create/modify:
  534.         // WCAP_STORE_TYPE_CREATE, WCAP_STORE_TYPE_MODIFY
  535.         params += (bAddItem ? "&storetype=1" : "&storetype=2");
  536.         
  537.         if (bIsParent)
  538.             params += "&mod=4"; // THIS AND ALL INSTANCES
  539.         else {
  540.             var rid = item.recurrenceId;
  541.             if (rid.isDate) {
  542.                 // cs does not accept DATE:
  543.                 rid = rid.clone();
  544.                 rid.isDate = false;
  545.             }
  546.             params += ("&mod=1&rid=" + getIcalUTC(rid)); // THIS INSTANCE
  547.         }
  548.         
  549.         if (bOrgRequest)
  550.             params += "&method=2"; // REQUEST
  551.         else if (bAttendeeReply)
  552.             params += "&method=4"; // REPLY
  553.         // else PUBLISH (default)
  554.         
  555.         params += "&replace=1"; // (update) don't append to any lists    
  556.         params += "&fetch=1&relativealarm=1&compressed=1&recurring=1";
  557.         params += "&emailorcalid=1&fmt-out=text%2Fcalendar";
  558.         
  559.         this.issueNetworkRequest(request, netRespFunc, stringToIcal,
  560.                                  bIsEvent ? "storeevents" : "storetodos", params,
  561.                                  calIWcapCalendar.AC_COMP_READ |
  562.                                  calIWcapCalendar.AC_COMP_WRITE);
  563.     }
  564. };
  565.  
  566. calWcapCalendar.prototype.tunnelXProps =
  567. function calWcapCalendar_tunnelXProps(destItem, srcItem)
  568. {
  569.     // xxx todo: temp workaround for bug in calItemBase.js
  570.     if (!isParent(srcItem))
  571.         return;
  572.     // tunnel alarm X-MOZ-SNOOZE only if alarm is still set:
  573.     var alarmOffset = destItem.alarmOffset;
  574.     var enumerator = srcItem.propertyEnumerator;
  575.     while (enumerator.hasMoreElements()) {
  576.         try {
  577.             var prop = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
  578.             var name = prop.name;
  579.             if (name.indexOf("X-MOZ-") == 0) {
  580.                 switch (name) {
  581.                 // keep snooze stamps for occurrences only and if alarm is still set:
  582.                 case "X-MOZ-SNOOZE-TIME":
  583.                     if (!alarmOffset)
  584.                         break; // alarm has been reset
  585.                     // fallthru intended:
  586.                 default:
  587.                     if (LOG_LEVEL > 1)
  588.                         log("tunneling " + name + "=" + prop.value, this);
  589.                     destItem.setProperty(name, prop.value);
  590.                     break;
  591.                 }
  592.             }
  593.         }
  594.         catch (exc) {
  595.             logError(exc, this);
  596.         }
  597.     }
  598. };
  599.  
  600. calWcapCalendar.prototype.adoptItem =
  601. function calWcapCalendar_adoptItem(item, listener)
  602. {
  603.     var this_ = this;
  604.     var request = new calWcapRequest(
  605.         function adoptItem_resp(request, err, newItem) {
  606.             if (listener) {
  607.                 listener.onOperationComplete(this_.superCalendar,
  608.                                              getResultCode(err),
  609.                                              calIOperationListener.ADD,
  610.                                              err ? item.id : newItem.id,
  611.                                              err ? err : newItem);
  612.             }
  613.             if (err)
  614.                 this_.notifyError(err);
  615.             else
  616.                 this_.notifyObservers("onAddItem", [newItem]);
  617.         },
  618.         log("adoptItem() call: " + item.title, this));
  619.     
  620.     try {
  621.         if (!isParent(item)) {
  622.             this_.logError("adoptItem(): unexpected proxy!");
  623.             debugger;
  624.         }
  625.         this.storeItem(true/*bAddItem*/,
  626.                        item, null, request,
  627.                        function netResp(err, icalRootComp) {
  628.                            if (err)
  629.                                throw err;
  630.                            var items = this_.parseItems(
  631.                                icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS,
  632.                                0, null, null, true /* bLeaveMutable */);
  633.                            if (items.length < 1)
  634.                                throw new Components.Exception("empty VCALENDAR returned!");
  635.                            if (items.length > 1)
  636.                                this_.notifyError("unexpected number of items: " + items.length);
  637.                            var newItem = items[0];
  638.                            this_.tunnelXProps(newItem, item);
  639.                            item.makeImmutable();
  640.                            // invalidate cached results:
  641.                            delete this_.m_cachedResults;
  642.                            log("newItem.id=" + newItem.id, this_);
  643.                            // xxx todo: may log request status
  644.                            request.execRespFunc(null, newItem);
  645.                        });
  646.     }
  647.     catch (exc) {
  648.         request.execRespFunc(exc);
  649.     }
  650.     return request;
  651. }
  652.  
  653. calWcapCalendar.prototype.addItem =
  654. function calWcapCalendar_addItem(item, listener)
  655. {
  656.     this.adoptItem(item.clone(), listener);
  657. };
  658.  
  659. calWcapCalendar.prototype.modifyItem =
  660. function calWcapCalendar_modifyItem(newItem, oldItem, listener)
  661. {
  662.     var this_ = this;
  663.     var request = new calWcapRequest(
  664.         function modifyItem_resp(request, err, item) {
  665.             if (listener) {
  666.                 listener.onOperationComplete(
  667.                     this_.superCalendar, getResultCode(err),
  668.                     calIOperationListener.MODIFY,
  669.                     newItem.id, err ? err : item);
  670.             }
  671.             if (err)
  672.                 this_.notifyError(err);
  673.             else
  674.                 this_.notifyObservers("onModifyItem", [item, oldItem]);
  675.         },
  676.         log("modifyItem() call: " + newItem.id, this));
  677.     
  678.     try {
  679.         if (!newItem.id)
  680.             throw new Components.Exception("new item has no id!");
  681.         var oldItem_ = oldItem;
  682.         if (oldItem && !isParent(newItem) &&
  683.             !oldItem.parentItem.recurrenceInfo.getExceptionFor(newItem.recurrenceId, false)) {
  684.             // pass null for oldItem when creating new exceptions, write whole item:
  685.             oldItem_ = null;
  686.         }
  687.         this.storeItem(false/*bAddItem*/,
  688.                        newItem, oldItem_, request,
  689.                        function netResp(err, icalRootComp) {
  690.                            if (err)
  691.                                throw err;
  692.                            var items = this_.parseItems(
  693.                                icalRootComp,
  694.                                calICalendar.ITEM_FILTER_ALL_ITEMS,
  695.                                0, null, null, true /* bLeaveMutable */);
  696.                            if (items.length < 1)
  697.                                throw new Components.Exception("empty VCALENDAR returned!");
  698.                            if (items.length > 1)
  699.                                this_.notifyError("unexpected number of items: " + items.length);
  700.                            var item = items[0];
  701.                            this_.tunnelXProps(item, newItem);
  702.                            item.makeImmutable();
  703.                            // invalidate cached results:
  704.                            delete this_.m_cachedResults;
  705.                            // xxx todo: maybe log request status
  706.                            request.execRespFunc(null, item);
  707.                        });
  708.     }
  709.     catch (exc) {
  710.         request.execRespFunc(exc);
  711.     }
  712.     return request;
  713. };
  714.  
  715. calWcapCalendar.prototype.deleteItem =
  716. function calWcapCalendar_deleteItem(item, listener)
  717. {
  718.     var this_ = this;
  719.     var request = new calWcapRequest(
  720.         function deleteItem_resp(request, err) {
  721.             // xxx todo: need to notify about each deleted item if multiple?
  722.             if (listener) {
  723.                 listener.onOperationComplete(
  724.                     this_.superCalendar, getResultCode(err),
  725.                     calIOperationListener.DELETE,
  726.                     item.id, err ? err : item);
  727.             }
  728.             if (err)
  729.                 this_.notifyError(err);
  730.             else
  731.                 this_.notifyObservers("onDeleteItem", [item]);
  732.         },
  733.         log("deleteItem() call: " + item.id, this));
  734.     
  735.     try {
  736.         if (!item.id)
  737.             throw new Components.Exception("no item id!");
  738.         var params = ("&uid=" + encodeURIComponent(item.id));
  739.         if (isParent(item)) // delete THIS AND ALL:
  740.             params += "&mod=4&rid=0";
  741.         else { // delete THIS INSTANCE:
  742.             var rid = item.recurrenceId;
  743.             if (rid.isDate) {
  744.                 // cs does not accept DATE:
  745.                 rid = rid.clone();
  746.                 rid.isDate = false;
  747.             }
  748.             params += ("&mod=1&rid=" + getIcalUTC(rid));
  749.         }
  750.         params += "&fmt-out=text%2Fxml";
  751.         
  752.         this.issueNetworkRequest(
  753.             request,
  754.             function netResp(err, xml) {
  755.                 if (err)
  756.                     throw err;
  757.                 // invalidate cached results:
  758.                 delete this_.m_cachedResults;
  759.                 if (LOG_LEVEL > 0)
  760.                     log("deleteItem(): " + getWcapRequestStatusString(xml), this_);
  761.             },
  762.             stringToXml, isEvent(item) ? "deleteevents_by_id" : "deletetodos_by_id",
  763.             params, calIWcapCalendar.AC_COMP_WRITE);
  764.     }
  765.     catch (exc) {
  766.         request.execRespFunc(exc);
  767.     }
  768.     return request;
  769. };
  770.  
  771. calWcapCalendar.prototype.parseItems = function calWcapCalendar_parseItems(
  772.     icalRootComp, itemFilter, maxResults, rangeStart, rangeEnd, bLeaveMutable)
  773. {
  774.     var items = [];
  775.     var unexpandedItems = [];
  776.     var uid2parent = {};
  777.     var excItems = [];
  778.     
  779.     var componentType = "ANY";
  780.     switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) {
  781.     case calICalendar.ITEM_FILTER_TYPE_TODO:
  782.         componentType = "VTODO"; break;
  783.     case calICalendar.ITEM_FILTER_TYPE_EVENT:
  784.         componentType = "VEVENT"; break;
  785.     }
  786.     
  787.     var this_ = this;
  788.     forEachIcalComponent(
  789.         icalRootComp, componentType,
  790.         function( subComp )
  791.         {
  792.             function patchTimezone(subComp, attr, xprop) {
  793.                 var dt = subComp[attr];
  794.                 if (dt) {
  795.                     if (LOG_LEVEL > 2) {
  796.                         log(attr + " is " + dt, this_);
  797.                     }
  798.                     var tzid = subComp.getFirstProperty(xprop);
  799.                     if (tzid != null) {
  800.                         subComp[attr] = dt.getInTimezone(tzid.value);
  801.                         if (LOG_LEVEL > 2) {
  802.                             log("patching " + xprop + " from " +
  803.                                 dt + " to " + subComp[attr], this_);
  804.                         }
  805.                     }
  806.                 }
  807.             }
  808.  
  809.             patchTimezone(subComp, "startTime", "X-NSCP-DTSTART-TZID");
  810.             var item = null;
  811.             switch (subComp.componentType) {
  812.             case "VEVENT": {
  813.                 patchTimezone(subComp, "endTime", "X-NSCP-DTEND-TZID");
  814.                 item = new CalEvent();
  815.                 item.icalComponent = subComp;
  816.                 break;
  817.             }
  818.             case "VTODO": {
  819.                 patchTimezone(subComp, "dueTime", "X-NSCP-DUE-TZID");
  820.                 item = new CalTodo();
  821.                 item.icalComponent = subComp;
  822.                 switch (itemFilter & calICalendar.ITEM_FILTER_COMPLETED_ALL) {
  823.                     case calICalendar.ITEM_FILTER_COMPLETED_YES:
  824.                         if (!item.isCompleted) {
  825.                             delete item;
  826.                             item = null;
  827.                         }
  828.                     break;
  829.                     case calICalendar.ITEM_FILTER_COMPLETED_NO:
  830.                         if (item.isCompleted) {
  831.                             delete item;
  832.                             item = null;
  833.                         }
  834.                     break;
  835.                 }
  836.                 break;
  837.             }
  838.             }
  839.             if (item) {
  840.                 if (!item.title) {
  841.                     // assumed to look at a subscribed calendar,
  842.                     // so patch title for private items:
  843.                     switch (item.privacy) {
  844.                     case "PRIVATE":
  845.                         item.title = g_privateItemTitle;
  846.                         break;
  847.                     case "CONFIDENTIAL":
  848.                         item.title = g_confidentialItemTitle;
  849.                         break;
  850.                     }
  851.                 }
  852.                 
  853.                 item.calendar = this_.superCalendar;
  854.                 var rid = item.recurrenceId;
  855.                 if (rid) {
  856.                     item.recurrenceInfo = null;
  857.                     if (LOG_LEVEL > 1) {
  858.                         log("exception item: " + item.title +
  859.                             "\nrid=" + rid.icalString +
  860.                             "\nitem.id=" + item.id, this_);
  861.                     }
  862.                     excItems.push(item);
  863.                 }
  864.                 else if (item.recurrenceInfo) {
  865.                     unexpandedItems.push(item);
  866.                     uid2parent[item.id] = item;
  867.                 }
  868.                 else if (maxResults == 0 || items.length < maxResults) {
  869.                     if (LOG_LEVEL > 2) {
  870.                         log("item: " + item.title + "\n" + item.icalString,
  871.                             this_);
  872.                     }
  873.                     if (!bLeaveMutable)
  874.                         item.makeImmutable();
  875.                     items.push(item);
  876.                 }
  877.             }
  878.         },
  879.         maxResults);
  880.     
  881.     // tag "exceptions", i.e. items with rid:
  882.     for each (var item in excItems) {
  883.         var parent = uid2parent[item.id];
  884.         if (parent) {
  885.             item.parentItem = parent;
  886.             var recStartDate = parent.recurrenceStartDate;
  887.             if (recStartDate && recStartDate.isDate && !item.recurrenceId.isDate) {
  888.                 // cs ought to return proper all-day RECURRENCE-ID!
  889.                 // get into startDate's timezone before cutting:
  890.                 var rid = item.recurrenceId.getInTimezone(recStartDate.timezone);
  891.                 rid.isDate = true;
  892.                 item.recurrenceId = rid;
  893.             }
  894.             item.makeImmutable();
  895.             parent.recurrenceInfo.modifyException(item);
  896.         }
  897.         else {
  898.             logError("parseItems(): no parent item for " + item.title +
  899.                      ", rid=" + item.recurrenceId.icalString +
  900.                      ", item.id=" + item.id, this);
  901.             // due to a server bug, in some scenarions the returned
  902.             // data is lacking the parent item, leave parentItem open then
  903.             if ((itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) == 0)
  904.                 item.recurrenceId = null;
  905.             if (!bLeaveMutable)
  906.                 item.makeImmutable();
  907.             items.push(item);
  908.         }
  909.     }
  910.     
  911.     if (itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) {
  912.         for each ( var item in unexpandedItems ) {
  913.             if (maxResults != 0 && items.length >= maxResults)
  914.                 break;
  915.             if (!bLeaveMutable)
  916.                 item.makeImmutable();
  917.             var occurrences = item.recurrenceInfo.getOccurrences(
  918.                 rangeStart, rangeEnd,
  919.                 maxResults == 0 ? 0 : maxResults - items.length,
  920.                 {} );
  921.             if (LOG_LEVEL > 1) {
  922.                 log("item: " + item.title + " has " +
  923.                     occurrences.length.toString() + " occurrences.", this);
  924.                 if (LOG_LEVEL > 2) {
  925.                     for each ( var occ in occurrences ) {
  926.                         log("item: " + occ.title + "\n" + occ.icalString, this);
  927.                     }
  928.                 }
  929.             }
  930.             // only proxies returned:
  931.             items = items.concat(occurrences);
  932.         }
  933.     }
  934.     else {
  935.         if (maxResults != 0 &&
  936.             (items.length + unexpandedItems.length) > maxResults) {
  937.             unexpandedItems.length = (maxResults - items.length);
  938.         }
  939.         if (!bLeaveMutable) {
  940.             for each ( var item in unexpandedItems ) {
  941.                 item.makeImmutable();
  942.             }
  943.         }
  944.         if (LOG_LEVEL > 2) {
  945.             for each ( var item in unexpandedItems ) {
  946.                 log("item: " + item.title + "\n" + item.icalString, this);
  947.             }
  948.         }
  949.         items = items.concat(unexpandedItems);
  950.     }
  951.     
  952.     if (LOG_LEVEL > 1)
  953.         log("parseItems(): returning " + items.length + " items", this);
  954.     return items;
  955. };
  956.  
  957. // calWcapCalendar.prototype.getItem = function( id, listener )
  958. // {
  959. //     // xxx todo: test
  960. //     // xxx todo: howto detect whether to call
  961. //     //           fetchevents_by_id ot fetchtodos_by_id?
  962. //     //           currently drag/drop is implemented for events only,
  963. //     //           try events first, fallback to todos... in the future...
  964. //     this.log( ">>>>>>>>>>>>>>>> getItem() call!");
  965. //     try {
  966. //         this.assureAccess(calIWcapCalendar.AC_COMP_READ);
  967.         
  968. //         var this_ = this;
  969. //         var syncResponseFunc = function( wcapResponse ) {
  970. //             var icalRootComp = wcapResponse.data; // first statement, may throw
  971. //             var items = this_.parseItems(
  972. //                 icalRootComp,
  973. //                 calICalendar.ITEM_FILTER_ALL_ITEMS,
  974. //                 1, null, null );
  975. //             if (items.length < 1)
  976. //                 throw new Components.Exception("no such item!");
  977. //             if (items.length > 1) {
  978. //                 this_.notifyError(
  979. //                     "unexpected number of items: " + items.length );
  980. //             }
  981. //             item = items[0];
  982. //             if (listener) {
  983. //                 listener.onGetResult(
  984. //                     this_.superCalendar, NS_OK,
  985. //                     calIItemBase,
  986. //                     log("getItem(): success.", this_),
  987. //                     items.length, items );
  988. //                 listener.onOperationComplete(
  989. //                     this_.superCalendar, NS_OK,
  990. //                     calIOperationListener.GET,
  991. //                     items.length == 1 ? items[0].id : null, null );
  992. //                 this_.log( "item delivered." );
  993. //             }
  994. //         };
  995.         
  996. //         var params = ("&relativealarm=1&compressed=1&recurring=1" +
  997. //                       "&emailorcalid=1&fmt-out=text%2Fcalendar");
  998. //         params += ("&uid=" + encodeURIComponent(id));
  999. //         try {
  1000. //             // xxx todo!!!!
  1001.  
  1002. //             // most common: event
  1003. //             this.session.issueSyncRequest(
  1004. //                 this.getCommandUrl( "fetchevents_by_id" ) + params,
  1005. //                 stringToIcal, syncResponseFunc );
  1006. //         }
  1007. //         catch (exc) {
  1008. //             // try again, may be a task:
  1009. //             this.session.issueSyncRequest(
  1010. //                 this.getCommandUrl( "fetchtodos_by_id" ) + params,
  1011. //                 stringToIcal, syncResponseFunc );
  1012. //         }
  1013. //     }
  1014. //     catch (exc) {
  1015. //         if (listener != null) {
  1016. //             listener.onOperationComplete(
  1017. //                 this.superCalendar, Components.results.NS_ERROR_FAILURE,
  1018. //                 calIOperationListener.GET,
  1019. //                 null, exc );
  1020. //         }
  1021. //         if (getResultCode(exc) == calIWcapErrors.WCAP_LOGIN_FAILED) {
  1022. //             // silently ignore login failed, no calIObserver UI:
  1023. //             this.logError( "getItem() ignored: " + errorToString(exc) );
  1024. //         }
  1025. //         else
  1026. //             this.notifyError( exc );
  1027. //     }
  1028. //     this.log( "getItem() returning." );
  1029. // };
  1030.  
  1031. function getItemFilterParams(itemFilter)
  1032. {
  1033.     var params = "";
  1034.     switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) {
  1035.     case calICalendar.ITEM_FILTER_TYPE_TODO:
  1036.         params += "&component-type=todo"; break;
  1037.     case calICalendar.ITEM_FILTER_TYPE_EVENT:
  1038.         params += "&component-type=event"; break;
  1039.     }
  1040.     
  1041.     var compstate = "";
  1042. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REPLY_DECLINED)
  1043. //         compstate += ";REPLY-DECLINED";
  1044. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REPLY_ACCEPTED)
  1045. //         compstate += ";REPLY-ACCEPTED";
  1046. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_COMPLETED)
  1047. //         compstate += ";REQUEST-COMPLETED";
  1048.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_NEEDS_ACTION)
  1049.         compstate += ";REQUEST-NEEDS-ACTION";
  1050.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_NEEDSNOACTION)
  1051.         compstate += ";REQUEST-NEEDSNOACTION";
  1052. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_PENDING)
  1053. //         compstate += ";REQUEST-PENDING";
  1054. //     if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_WAITFORREPLY)
  1055. //         compstate += ";REQUEST-WAITFORREPLY";
  1056.     if (compstate.length > 0)
  1057.         params += ("&compstate=" + compstate.substr(1));
  1058.     return params;
  1059. }
  1060.  
  1061. calWcapCalendar.prototype.getItems =
  1062. function calWcapCalendar_getItems(itemFilter, maxResults, rangeStart, rangeEnd, listener)
  1063. {
  1064.     // assure DATE-TIMEs:
  1065.     if (rangeStart && rangeStart.isDate) {
  1066.         rangeStart = rangeStart.clone();
  1067.         rangeStart.isDate = false;
  1068.     }
  1069.     if (rangeEnd && rangeEnd.isDate) {
  1070.         rangeEnd = rangeEnd.clone();
  1071.         rangeEnd.isDate = false;
  1072.     }
  1073.     var zRangeStart = getIcalUTC(rangeStart);
  1074.     var zRangeEnd = getIcalUTC(rangeEnd);
  1075.     
  1076.     var this_ = this;
  1077.     var request = new calWcapRequest(
  1078.         function getItems_resp(request, err, data) {
  1079.             var rc = getResultCode(err);
  1080.             if (err) {
  1081.                 if (listener) {
  1082.                     listener.onOperationComplete(
  1083.                         this_.superCalendar, rc,
  1084.                         calIOperationListener.GET,
  1085.                         null, err);
  1086.                 }
  1087.                 this_.notifyError(err, request.suppressOnError);
  1088.             }
  1089.             else {
  1090.                 log("getItems(): success.", this_);
  1091.                 if (listener) {
  1092.                     listener.onOperationComplete(
  1093.                         this_.superCalendar, rc,
  1094.                         calIOperationListener.GET,
  1095.                         null, null);
  1096.                 }
  1097.             }
  1098.         },
  1099.         log("getItems():\n\titemFilter=0x" + itemFilter.toString(0x10) +
  1100.             ",\n\tmaxResults=" + maxResults +
  1101.             ",\n\trangeStart=" + zRangeStart +
  1102.             ",\n\trangeEnd=" + zRangeEnd, this));
  1103.     
  1104.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR)
  1105.         request.suppressOnError = true;
  1106.     
  1107.     if (this.aboutToBeUnregistered) {
  1108.         // limiting the amount of network traffic while unregistering
  1109.         log("being unregistered, no results.", this);
  1110.         request.execRespFunc(null, []);
  1111.         return request;
  1112.     }
  1113.     
  1114.     // m_cachedResults holds the last data revtrieval. This is expecially useful when
  1115.     // switching on multiple subcriptions: the composite calendar multiplexes getItems()
  1116.     // calls to all composited calendars over and over again, most often on the same
  1117.     // date range (as the user usually looks at the same view).
  1118.     // This will most likely vanish when a better caching is implemented in the views,
  1119.     // or WCAP local storage caching has sufficient performance.
  1120.     // The cached results will be invalidated after 2 minutes to reflect incoming invitations.
  1121.     if (CACHE_LAST_RESULTS > 0 && this.m_cachedResults) {
  1122.         for each (var entry in this.m_cachedResults) {
  1123.             if ((itemFilter == entry.itemFilter) &&
  1124.                 equalDatetimes(rangeStart, entry.rangeStart) &&
  1125.                 equalDatetimes(rangeEnd, entry.rangeEnd)) {
  1126.                 log("reusing last getItems() cached data.", this);
  1127.                 if (listener) {
  1128.                     listener.onGetResult(
  1129.                         this.superCalendar, NS_OK, calIItemBase,
  1130.                         "getItems()", entry.results.length, entry.results);
  1131.                 }
  1132.                 request.execRespFunc(null, entry.results);
  1133.                 return request;
  1134.             }
  1135.         }
  1136.     }
  1137.     
  1138.     try {
  1139.         var params = ("&relativealarm=1&compressed=1&recurring=1" +
  1140.                       "&emailorcalid=1&fmt-out=text%2Fcalendar");
  1141.         // setting component-type, compstate filters:
  1142.         params += getItemFilterParams(itemFilter);
  1143.         if (maxResults > 0)
  1144.             params += ("&maxResults=" + maxResults);
  1145.         params += ("&dtstart=" + zRangeStart);
  1146.         params += ("&dtend=" + zRangeEnd);
  1147.         
  1148.         this.issueNetworkRequest(
  1149.             request,
  1150.             function netResp(err, icalRootComp) {
  1151.                 if (err) {
  1152.                     if (getResultCode(err) ==
  1153.                         calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR)
  1154.                     {
  1155.                         // try free-busy times:
  1156.                         if (listener &&
  1157.                             (itemFilter & calICalendar.ITEM_FILTER_TYPE_EVENT) &&
  1158.                             rangeStart && rangeEnd)
  1159.                         {
  1160.                             var freeBusyListener = { // calIWcapRequestResultListener:
  1161.                                 onRequestResult:
  1162.                                 function freeBusyListener_onRequestResult(request, result) {
  1163.                                     if (!request.succeeded)
  1164.                                         throw request.status;
  1165.                                     var items = [];
  1166.                                     for each (var period in result) {
  1167.                                         var item = new CalEvent();
  1168.                                         item.id = (g_busyPhantomItemUuidPrefix +
  1169.                                                    getIcalUTC(period.start));
  1170.                                         item.calendar = this_.superCalendar;
  1171.                                         item.title = g_busyItemTitle;
  1172.                                         item.startDate = period.start;
  1173.                                         item.endDate = period.end;
  1174.                                         item.makeImmutable();
  1175.                                         items.push(item);
  1176.                                     }
  1177.                                     listener.onGetResult(
  1178.                                         this_.superCalendar, NS_OK, calIItemBase,
  1179.                                         "getItems()/free-busy", items.length, items);
  1180.                                 }
  1181.                             };
  1182.                             request.attachSubRequest(
  1183.                                 this_.session.getFreeBusyTimes(
  1184.                                     this_.calId, rangeStart, rangeEnd, true /*bBusy*/,
  1185.                                     freeBusyListener));
  1186.                         }
  1187.                     }
  1188.                     else
  1189.                         throw err;
  1190.                 }
  1191.                 else if (listener) {
  1192.                     var items = this_.parseItems(
  1193.                         icalRootComp, itemFilter, maxResults,
  1194.                         rangeStart, rangeEnd);
  1195.                     
  1196.                     if (CACHE_LAST_RESULTS > 0) {
  1197.                         // auto invalidate after X minutes:
  1198.                         if (!this_.m_cachedResultsTimer) {
  1199.                             var callback = {
  1200.                                 notify: function notify(timer) {
  1201.                                     if (!this_.m_cachedResults)
  1202.                                         return;
  1203.                                     var now = (new Date()).getTime();
  1204.                                     // sort out old entries:
  1205.                                     var entries = [];
  1206.                                     for (var i = 0; i < this_.m_cachedResults.length; ++i) {
  1207.                                         var entry = this_.m_cachedResults[i];
  1208.                                         if ((now - entry.stamp) <
  1209.                                             (CACHE_LAST_RESULTS_INVALIDATE * 1000)) {
  1210.                                             entries.push(entry);
  1211.                                         }
  1212.                                         else {
  1213.                                             log("invalidating cached entry:\n\trangeStart=" +
  1214.                                                 getIcalUTC(entry.rangeStart) + "\n\trangeEnd=" +
  1215.                                                 getIcalUTC(entry.rangeEnd), this_);
  1216.                                         }
  1217.                                     }
  1218.                                     this_.m_cachedResults = entries;
  1219.                                 }
  1220.                             };
  1221.                             // sort out freq:
  1222.                             var freq = Math.min(
  1223.                                 20, // default: 20secs
  1224.                                 Math.max(1, CACHE_LAST_RESULTS_INVALIDATE));
  1225.                             log("cached results sort out timer freq: " + freq, this_);
  1226.                             this_.m_cachedResultsTimer = new Timer();
  1227.                             this_.m_cachedResultsTimer.initWithCallback(
  1228.                                 callback, freq * 1000,
  1229.                                 Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
  1230.                         }
  1231.                         if (!this_.m_cachedResults)
  1232.                             this_.m_cachedResults = [];
  1233.                         var entry = {
  1234.                             stamp: (new Date()).getTime(),
  1235.                             itemFilter: itemFilter,
  1236.                             rangeStart: (rangeStart ? rangeStart.clone() : null),
  1237.                             rangeEnd: (rangeEnd ? rangeEnd.clone() : null),
  1238.                             results: items
  1239.                         };
  1240.                         this_.m_cachedResults.unshift(entry);
  1241.                         if (this_.m_cachedResults.length > CACHE_LAST_RESULTS)
  1242.                             this_.m_cachedResults.length = CACHE_LAST_RESULTS;
  1243.                     }
  1244.                     
  1245.                     listener.onGetResult(this_.superCalendar, NS_OK, calIItemBase,
  1246.                                          "getItems()", items.length, items);
  1247.                 }
  1248.             },
  1249.             stringToIcal,
  1250.             (itemFilter & calIWcapCalendar.ITEM_FILTER_BY_ALARM_RANGE)
  1251.             ? "fetchcomponents_by_alarmrange" : "fetchcomponents_by_range",
  1252.             params, calIWcapCalendar.AC_COMP_READ);
  1253.     }
  1254.     catch (exc) {
  1255.         request.execRespFunc(exc);
  1256.     }
  1257.     return request;
  1258. };
  1259.  
  1260. // function calWcapSyncOperationListener() {
  1261. //     this.superClass(respFunc);
  1262. //     this.wrappedJSObject = this;
  1263. // }
  1264. // subClass(calWcapSyncOperationListener, calWcapRequest);
  1265.  
  1266. // calWcapSyncOperationListener.prototype.QueryInterface =
  1267. // function calWcapSyncOperationListener_QueryInterface(iid) {
  1268. //     // xxx todo:
  1269. //     const m_ifaces = [ Components.interfaces.nsISupports,
  1270. //                        Components.interfaces.calIOperationListener,
  1271. //                        Components.interfaces.calIWcapRequest ];
  1272. //     qiface(m_ifaces, iid);
  1273. //     return this;
  1274. // };
  1275.  
  1276. // // calIOperationListener:
  1277. // calWcapSyncOperationListener.prototype.onOperationComplete =
  1278. // function calWcapSyncOperationListener_onOperationComplete(
  1279. //     calendar, status, opType, id, detail)
  1280. // {
  1281. //     if (status != NS_OK) {
  1282. //         this.
  1283. //             this.m_syncState.abort( detail );
  1284. //     }
  1285. //     else if (this.m_opType != opType) {
  1286. //         this.m_syncState.abort(
  1287. //             new Components.Exception("unexpected operation type: " +
  1288. //                                      opType) );
  1289. //     }
  1290. //     this.m_syncState.release();
  1291. // };
  1292.  
  1293. // calWcapSyncOperationListener.prototype.onGetResult =
  1294. // function calWcapSyncOperationListener_onGetResult(
  1295. //     calendar, status, itemType, detail, count, items)
  1296. // {
  1297. //     this.m_syncState.abort(
  1298. //         new Components.Exception("unexpected onGetResult()!") );
  1299. // };
  1300.  
  1301. // calWcapCalendar.prototype.syncChangesTo_resp = function(
  1302. //     wcapResponse, syncState, listener, func )
  1303. // {
  1304. //     try {
  1305. //         var icalRootComp = wcapResponse.data; // first statement, may throw
  1306. //         var items = this.parseItems_(
  1307. //             function(items) { items.forEach(func) },
  1308. //             icalRootComp,
  1309. //             calICalendar.ITEM_FILTER_ALL_ITEMS,
  1310. //             0, null, null );
  1311. //     }
  1312. //     catch (exc) {
  1313. //         syncState.abort( exc );
  1314. //     }
  1315. //     syncState.release();
  1316. // };
  1317.  
  1318. calWcapCalendar.prototype.syncChangesTo =
  1319. function calWcapCalendar_syncChangesTo(destCal, itemFilter, dtFrom_, listener)
  1320. {
  1321.     // xxx todo: move to Thomas
  1322.     // do NOT puke up error box every three minutes!
  1323.     itemFilter |= calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR;
  1324.     
  1325.     var now = getTime(); // new stamp for this sync
  1326.     var this_ = this;
  1327.     var request_ = new calWcapRequest(
  1328.         function syncChangesTo_resp(request, err) {
  1329.             if (err) {
  1330.                 log("SYNC failed!", this_);
  1331.                 if (listener) {
  1332.                     listener.onOperationComplete(
  1333.                         this_.superCalendar, getResultCode(err),
  1334.                         calIWcapCalendar.SYNC, null, err);
  1335.                 }
  1336.                 this_.notifyError(err, request.suppressOnError);
  1337.             }
  1338.             else {
  1339.                 log("SYNC succeeded.", this_);
  1340.                 if (listener) {
  1341.                     listener.onOperationComplete(
  1342.                         this_.superCalendar, NS_OK,
  1343.                         calIWcapCalendar.SYNC, null, now);
  1344.                 }
  1345.             }
  1346.         },
  1347.         log("syncChangesTo():\n\titemFilter=0x" + itemFilter.toString(0x10) +
  1348.             "\n\tdtFrom_=" + getIcalUTC(dtFrom_), this));
  1349.     
  1350.     if (itemFilter & calIWcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR)
  1351.         request_.suppressOnError = true;
  1352.     
  1353.     try {
  1354.         var dtFrom = dtFrom_;
  1355.         if (dtFrom) {
  1356.             dtFrom = dtFrom.clone();
  1357.             // assure DATE-TIME:
  1358.             if (dtFrom.isDate)
  1359.                 dtFrom.isDate = false;
  1360.             dtFrom = this.session.getServerTime(dtFrom);
  1361.         }
  1362.         var zdtFrom = getIcalUTC(dtFrom);
  1363.         
  1364.         var calObserver = null;
  1365.         if (listener) {
  1366.             try {
  1367.                 calObserver = listener.QueryInterface(
  1368.                     Components.interfaces.calIObserver);
  1369.             }
  1370.             catch (exc) {
  1371.             }
  1372.         }
  1373.         
  1374.         var request = new calWcapRequest(
  1375.             function netFinishedRespFunc(err, data) {
  1376.                 var modifiedIds = {};
  1377.                 for each (var item in request.m_modifiedItems) {
  1378.                     var dtCreated = item.getProperty("CREATED");
  1379.                     var bAdd = (!dtCreated || !dtFrom ||
  1380.                                 dtCreated.compare(dtFrom) >= 0);
  1381.                     modifiedIds[item.id] = true;
  1382.                     if (bAdd) {
  1383.                         // xxx todo: verify whether exceptions
  1384.                         //           have been written
  1385.                         log("syncChangesTo(): new item " + item.id, this_);
  1386.                         if (destCal) {
  1387. //                                 destCal.addItem(item, addItemListener);
  1388.                         }
  1389.                         if (calObserver)
  1390.                             calObserver.onAddItem(item);
  1391.                     }
  1392.                     else {
  1393.                         log("syncChangesTo(): modified item " + item.id, this_);
  1394.                         if (destCal) {
  1395. //                             destCal.modifyItem(item, null, modifyItemListener);
  1396.                         }
  1397.                         if (calObserver)
  1398.                             calObserver.onModifyItem(item, null);
  1399.                     }
  1400.                 }
  1401.                 for each (var item in request.m_deletedItems) {
  1402.                     // don't delete anything that has been touched by lastmods:
  1403.                     if (modifiedIds[item.id])
  1404.                         log("syncChangesTo(): skipping deletion of " + item.id, this_);
  1405.                     else if (isParent(item)) {
  1406.                         log("syncChangesTo(): deleted item " + item.id, this_);
  1407.                         if (destCal) {
  1408. //                             destCal.deleteItem(item, deleteItemListener);
  1409.                         }
  1410.                         if (calObserver)
  1411.                             calObserver.onDeleteItem(item);
  1412.                     }
  1413.                     else { // modify parent instead of
  1414.                            // straight-forward deleteItem(). WTF.
  1415.                         var parent = item.parentItem.clone();
  1416.                         parent.recurrenceInfo.removeOccurrenceAt(item.recurrenceId);
  1417.                         log("syncChangesTo(): modified parent "+ parent.id, this_);
  1418.                         if (destCal) {
  1419. //                             destCal.modifyItem(parent, item, deleteItemListener);
  1420.                         }
  1421.                         if (calObserver)
  1422.                             calObserver.onModifyItem(parent, item);
  1423.                     }
  1424.                 }
  1425.             }, "syncChangesTo() netFinishedRespFunc");
  1426.         request_.attachSubRequest(request);
  1427.         
  1428.         var params = ("&relativealarm=1&compressed=1&recurring=1" +
  1429.                       "&emailorcalid=1&fmt-out=text%2Fcalendar");
  1430.         params += ("&dtstart=" + zdtFrom);
  1431.         params += ("&dtend=" + getIcalUTC(this.session.getServerTime(now)));
  1432.         
  1433.         log("syncChangesTo(): getting last modifications...", this);
  1434.         this.issueNetworkRequest(
  1435.             request,
  1436.             function modifiedNetResp(err, icalRootComp) {
  1437.                 if (err)
  1438.                     throw err;
  1439.                 request.m_modifiedItems = this_.parseItems(
  1440.                     icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
  1441.             },
  1442.             stringToIcal, "fetchcomponents_by_lastmod",
  1443.             params + getItemFilterParams(itemFilter),
  1444.             calIWcapCalendar.AC_COMP_READ);        
  1445.         
  1446.         log("syncChangesTo(): getting deleted items...", this);
  1447.         this.issueNetworkRequest(
  1448.             request,
  1449.             function modifiedNetResp(err, icalRootComp) {
  1450.                 if (err)
  1451.                     throw err;
  1452.                 request.m_deletedItems = this_.parseItems(
  1453.                     icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null);
  1454.             },
  1455.             stringToIcal, "fetch_deletedcomponents",
  1456.             params + getItemFilterParams(itemFilter & // only component types
  1457.                                          calICalendar.ITEM_FILTER_TYPE_ALL),
  1458.             calIWcapCalendar.AC_COMP_READ);
  1459.     }
  1460.     catch (exc) {
  1461.         request_.execRespFunc(exc);
  1462.     }
  1463.     return request_;
  1464. };
  1465.  
  1466.