diff --git a/calendar/providers/caldav/calDavCalendar.js b/calendar/providers/caldav/calDavCalendar.js index a67f9b22a2f5..3af6577f2e45 100644 --- a/calendar/providers/caldav/calDavCalendar.js +++ b/calendar/providers/caldav/calDavCalendar.js @@ -67,6 +67,7 @@ const nsIWebDAVOperationListener = const calICalendar = Components.interfaces.calICalendar; const nsISupportsCString = Components.interfaces.nsISupportsCString; const calIEvent = Components.interfaces.calIEvent; +const calITodo = Components.interfaces.calITodo; const calEventClass = Components.classes["@mozilla.org/calendar/event;1"]; const calIItemOccurrence = Components.interfaces.calIItemOccurrence; @@ -74,10 +75,8 @@ function makeOccurrence(item, start, end) { var occ = Components.classes["@mozilla.org/calendar/item-occurrence;1"]. createInstance(calIItemOccurrence); - // XXX poor form - occ.wrappedJSObject.item = item; - occ.wrappedJSObject.occurrenceStartDate = start; - occ.wrappedJSObject.occurrenceEndDate = end; + occ.initialize(item, start, end); + return occ; } @@ -119,7 +118,7 @@ calDavCalendar.prototype = { { return; } - } + } this.mObservers.push( {observer: aObserver, filter: aItemFilter} ); }, @@ -129,7 +128,7 @@ calDavCalendar.prototype = { var newObservers = Array(); for (var i = 0; i < this.mObservers.length; i++) { if (this.mObservers[i].observer != aObserver) - newObservers.push(mObservers[i]); + newObservers.push(this.mObservers[i]); } this.mObservers = newObservers; }, @@ -334,317 +333,204 @@ calDavCalendar.prototype = { // void getItem( in string id, in calIOperationListener aListener ); getItem: function (aId, aListener) { + if (!aListener) return; - // XXX we really should use DASL SEARCH or CalDAV REPORT, but the only - // server we can possibly test against doesn't yet support those - - function searchItemsLocally(calendarToReturn, aAllItems) { - - if (aId == null || aAllItems[aId] == null) { - aListener.onOperationComplete(this, - Components.results.NS_ERROR_FAILURE, - aListener.GET, - null, - "IID doesn't exist for getItem"); - // XXX FAILURE is a bad choice - return; - } - - var item = aAllItems[aId]; - var iid = null; - - if (item.QueryInterface(calIEvent)) { - iid = calIEvent; - } else if (item.QueryInterface(Components.interfaces.calITodo)) { - iid = Components.interfaces.calITodo; - } else { - aListener.onOperationComplete(this, - Components.results.NS_ERROR_FAILURE, - aListener.GET, - aId, - "Can't deduce item type based on QI"); - return; - } - - aListener.onGetResult(calendarToReturn, Components.results.NS_OK, - iid, null, 1, [item]); - - aListener.onOperationComplete(calendarToReturn, - Components.results.NS_OK, - aListener.GET, aId, null); + if (aId == null) { + // XXX FAILURE is a bad choice + aListener.onOperationComplete(this, + Components.results.NS_ERROR_FAILURE, + aListener.GET, + null, + "passed in empty iid"); + return; } - this.getAllEvents(aListener, searchItemsLocally); + // this is our basic search-by-uid xml + // XXX get rid of vevent filter? + // XXX need a prefix in the namespace decl? + default xml namespace = "urn:ietf:params:xml:ns:caldav"; + queryXml = + + + + + + + + {aId} + + + + + + ; + + this.reportInternal(queryXml.toXMLString(), false, aListener); return; }, + reportInternal: function (aQuery, aOccurrences, aListener) + { + var reportListener = new WebDavListener(); + + reportListener.onOperationDetail = function(aStatusCode, aResource, + aOperation, aDetail, + aClosure) { + var rv; + var errString; + + // is this detail the entire search result, rather than a single + // detail within a set? + // + if (aResource.path == calendarDirUri.path) { + // XXX is this even valid? what should we do here? + throw("XXX report result for calendar, not event\n"); + } + + // XXX need to do better than looking for just 200 + if (aStatusCode == 200) { + + // aDetail is the response element from the multi-status + // XXX try-catch + var xSerializer = Components.classes + ['@mozilla.org/xmlextras/xmlserializer;1'] + .getService(Components.interfaces.nsIDOMSerializer); + //XXXdump(xSerializer.serializeToString(aDetail)); + responseElement = new XML( + xSerializer.serializeToString(aDetail)); + + // create calIItemBase from e4x object + // XXX error-check that we only have one result, etc + var C = new Namespace("urn:ietf:params:xml:ns:caldav"); + + // create a local event item + var item = calEventClass.createInstance(calIEvent); + + // cause returned data to be parsed into the event item + // XXX try-catch + item.icalString = + responseElement..C::["calendar-query-result"]; + + // figure out what type of item to return + var iid; + if(aOccurrences) { + iid = calIItemOccurrence; + item = makeOccurrence(item, item.startDate, item.endDate); + } else if (item.QueryInterface(calIEvent)) { + iid = calIEvent; + rv = Components.results.NS_OK + } else if (item.QueryInterface(calITodo)) { + iid = calITodo; + } else { + errString = "Can't deduce item type based on query"; + rv = Components.results.NS_ERROR_FAILURE; + } + + } else { + // XXX + errString = "XXX"; + rv = Components.results.NS_ERROR_FAILURE; + } + + dump("errString = " + errString + "\n"); + dump("item = " + item + "\n"); + aListener.onGetResult(this, rv, iid, null, 1, + errString ? errString : [item]); + return; + }; + + reportListener.onOperationComplete = function(aStatusCode, aResource, + aOperation, aClosure) { + + // XXX test that something reasonable happens w/notfound + + // parse aStatusCode + var rv; + var errString; + if (aStatusCode == 200) { // XXX better error checking + rv = Components.results.NS_OK; + } else { + rv = Components.results.NS_ERROR_FAILURE; + errString = "XXX something bad happened"; + } + + // call back the listener + aListener.onOperationComplete(this, + Components.results.NS_ERROR_FAILURE, + aListener.GET, null, errString); + return; + }; + + // convert this into a form the WebDAV service can use + // XXX is "text/xml" really the right type? + var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1'] + .getService(Components.interfaces.nsIDOMParser); + queryDoc = xParser.parseFromString(aQuery, "text/xml"); + + // construct the resource we want to search against + // XXX adding "calendar/" to the uri is wrong + var calendarDirUri = this.mUri.clone(); + calendarDirUri.spec = calendarDirUri.spec + "calendar/"; + var calendarDirResource = new WebDavResource(calendarDirUri); + + var webSvc = Components.classes['@mozilla.org/webdav/service;1'] + .getService(Components.interfaces.nsIWebDAVService); + webSvc.report(calendarDirResource, queryDoc, true, reportListener, + null); + return; + + }, + // void getItems( in unsigned long aItemFilter, in unsigned long aCount, // in calIDateTime aRangeStart, in calIDateTime aRangeEnd, // in calIOperationListener aListener ); getItems: function (aItemFilter, aCount, aRangeStart, aRangeEnd, aListener) { - // XXX we really should use DASL SEARCH or CalDAV REPORT, but the only - // server we can possibly test against doesn't yet support those - - // this code copy-pasted from calMemoryCalendar.getItems() - function searchItemsLocally(calendarToReturn, aAllItems){ - const calIItemBase = Components.interfaces.calIItemBase; - const calITodo = Components.interfaces.calITodo; - const calIRecurrenceInfo = Components.interfaces.calIRecurrenceInfo; - - var itemsFound = Array(); - var startTime = START_OF_TIME; - var endTime = END_OF_TIME; - if (aRangeStart) - startTime = aRangeStart.nativeTime; - if (aRangeEnd) - endTime = aRangeEnd.nativeTime; - - // - // filters - // - - // item base type - var itemTypeFilter = null; - if (aItemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) - itemTypeFilter = calIItemBase; - else if (aItemFilter & calICalendar.ITEM_FILTER_TYPE_EVENT) - itemTypeFilter = calIEvent; - else if (aItemFilter & calICalendar.ITEM_FILTER_TYPE_TODO) - itemTypeFilter = calITodo; - else { - // bail. - aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, - aListener.GET, - null, - "Bad aItemFilter passed to getItems"); - return; - } - - // completed? - var itemCompletedFilter = ((aItemFilter & calICalendar.ITEM_FILTER_COMPLETED_YES) != 0); - var itemNotCompletedFilter = ((aItemFilter & calICalendar.ITEM_FILTER_COMPLETED_NO) != 0); - - // return occurrences? - var itemReturnOccurrences = ((aItemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0); - - // if aCount != 0, we don't attempt to sort anything, and - // instead return the first aCount items that match. - - for (var i in aAllItems) { - var item = aAllItems[i]; - var itemtoadd = null; - if (itemTypeFilter && !(item instanceof itemTypeFilter)) - continue; - - var itemStartTime = 0; - var itemEndTime = 0; - - var tmpitem = item; - if (item instanceof calIEvent) { - tmpitem = item.QueryInterface(calIEvent); - itemStartTime = item.startDate.valid ? item.startDate.nativeTime : - START_OF_TIME; - itemEndTime = item.endDate.valid ? item.endDate.nativeTime : - END_OF_TIME - - } else if (item instanceof calITodo) { - // if it's a todo, also filter based on completeness - if (item.percentComplete == 100 && !itemCompletedFilter) - continue; - else if (item.percentComplete < 100 && !itemNotCompletedFilter) - continue; - - itemEndTime = itemStartTime = item.entryTime.nativeTime || 0; - } else { - // XXX unknown item type, wth do we do? - continue; - } - - - if (itemStartTime <= endTime) { - // figure out if there are recurrences here we care about - if (itemReturnOccurrences && item.recurrenceInfo) - { - // there might be some recurrences here that we need to handle - var recs = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {}); - for (var i = 0; i < recs.length; i++) { - itemsFound.push(recs[i]); - } - } else if (itemEndTime >= startTime) { - // no occurrences - if (itemReturnOccurrences) - itemtoadd = makeOccurrence(item, item.startDate, item.endDate); - else - itemtoadd = item; - itemsFound.push(itemtoadd); - } - } - - if (aCount && itemsFound.length >= aCount) - break; - } - - dump("itemsFound = " + itemsFound + "\n"); - aListener.onGetResult (calendarToReturn, - Components.results.NS_OK, - itemReturnOccurrences ? calIItemOccurrence : itemTypeFilter, - null, - itemsFound.length, - itemsFound); - - aListener.onOperationComplete (calendarToReturn, - Components.results.NS_OK, - aListener.GET, - null, - null); + if (!aListener) return; - }; - // XXX for now, we're just gonna start off getting events. - // worry about todos and other stuff later - this.getAllEvents(aListener, searchItemsLocally); + // this is our basic report xml + // XXX get rid of vevent filter? + var C = new Namespace("urn:ietf:params:xml:ns:caldav") + default xml namespace = C; + var queryXml = + + + + + + + ; + + // XXX aCount + + // if a time range has been specified, do the appropriate restriction. + // XXX no way to express "end of time" in caldav in either direction + if (aRangeStart && aRangeStart.valid && + aRangeEnd && aRangeEnd.valid) { + + var rangeXml = ; + + // append the time-range as a child of our innermost + // icalcomp-filter + queryXml[0].C::filter.C::["icalcomp-filter"] + .C::["icalcomp-filter"].appendChild(rangeXml); + } + + // XXX aItemFilter + + var queryString = queryXml.toXMLString(); + dump("queryString = " + queryString + "\n"); + var occurrences = (aItemFilter & + calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0; + this.reportInternal(queryString, occurrences, aListener); }, - // A nasty little hack until there is a CalDAV server in existence to - // test against which implements DASL SEARCH or CalDAV REPORT. Basically - // we get all the events here, and then the caller specifies an - // aInternalCallback function that it uses to do the actual searching - // and callbacks - // - getAllEvents: function(aListener, aInternalCallback) { - if (!aListener) { - throw Components.results.NS_ERROR_ILLEGAL_VALUE; - } - - var webSvc = Components.classes['@mozilla.org/webdav/service;1'] - .getService(Components.interfaces.nsIWebDAVService); - - // So we need a list of items to iterate over. Should theoretically - // use search to find this out - var eventDirUri = this.mUri.clone(); - eventDirUri.spec = eventDirUri.spec + "calendar/"; - var eventDirResource = new WebDavResource(eventDirUri); - - var propsToGet = ["DAV: getlastmodified"]; - - var listener = new WebDavListener(); - var allItems = new Object(); - var listCompleted = false; - var itemsPending = 0; - var itemsCompleted = 0; - listener.onOperationDetail = function(aStatusCode, aResource, - aOperation, aDetail, aClosure) { - if (aStatusCode != 200) { - // this property had an error - - // XXX report error to caller, or just log? - dump("calDavCalendar.listener.onOperationDetail: " + - " property " + aResource + " returned error " + - aStatusCode + "\n"); - - return; - } - - var getListener = new WebDavListener(); - getListener.onOperationDetail = function(aStatusCode, aResource, - aOperation, aDetail, - aClosure) { - - dump("getListener.onOperationDetail called\n"); - if (aStatusCode == 200) { - - // turn the detail item into a datatype we can deal with - aDetail.QueryInterface(nsISupportsCString); - - // create a local event item - item = calEventClass.createInstance(calIEvent); - - // cause returned data to be parsed into the event item - item.icalString = aDetail.data; - - // add the event to the array of all items - allItems[item.id] = item; - dump("getListener.onOperationDetail: item " + item.id + - " found\n"); - - } else { - // XXX do real error handling here - dump("getListener.onOperationDetail: aStatusCode = " + - aStatusCode + "\n"); - } - - }; - - getListener.onOperationComplete = function(aStatusCode, - aResource, aOperation, - aClosure) { - dump("getListener.onOperationComplete() called\n"); - - if (aStatusCode != 200) { - dump("getListener.onOperationComplete: " + - " property " + aResource + " returned error " + - aStatusCode + "\n"); - - aClosure.onGetResult();// XXX call back error - } - - // we've finished this get; make a note of that - ++itemsCompleted; - - // if this was the last get we were waiting on, we're done - if ( listCompleted && itemsPending == itemsCompleted) { - - // we're done with all the gets - aInternalCallback(this, allItems); - } - }; - - // we don't care about the directory itself, only the children - // (think of the children!!) This is a gross hack, but it'll go - // away once we start using REPORT or SEARCH - // - if (aResource.path == eventDirUri.path) { - return; - } - - // make a note that this request is pending - ++itemsPending; - - webSvc.getToString(new WebDavResource(aResource), getListener, - null); - - } - listener.onOperationComplete = function(aStatusCode, aResource, - aOperation, aClosure) { - listCompleted = true; - - // if something went wrong while listing, call the error - // back to the caller, and then give up. - - if (aStatusCode != 207 && aStatusCode != 200) { - - // XXX test to find out return code if no children of events - - dump("calDavCalendar.listener.onOperationComplete: " + - " property " + aResource + " returned error " + - aStatusCode + "\n"); - - // aClosure contains the calIOperationListener - aClosure.onOperationComplete(this, aStatusCode, - calICalendar.GET, null, null); - } - } - - webSvc.getResourceProperties(eventDirResource, propsToGet.length, - propsToGet, true, listener, aListener); - }, - - // // Helper functions //