gecko-dev/calendar/providers/caldav/calDavCalendar.js

947 строки
34 KiB
JavaScript

/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Oracle Corporation code.
*
* The Initial Developer of the Original Code is
* Oracle Corporation
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
* Dan Mosedale <dan.mosedale@oracle.com>
* Mike Shaver <mike.x.shaver@oracle.com>
* Gary van der Merwe <garyvdm@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
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
//
// calDavCalendar.js
//
// XXXdmose deal with generation goop
// XXXdmose need to re-query for add & modify to get up-to-date items
// XXXdmose deal with etags
// XXXdmose deal with locking
// XXXdmose need to make and use better error reporting interface for webdav
// (all uses of aStatusCode, probably)
// XXXdmose use real calendar result codes, not NS_ERROR_FAILURE for everything
const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
function debug(s) {
const debugging = true;
if (debugging) {
dump(s);
}
}
function calDavCalendar() {
this.wrappedJSObject = this;
this.mObservers = Array();
}
// some shorthand
const nsIWebDAVOperationListener =
Components.interfaces.nsIWebDAVOperationListener;
const calICalendar = Components.interfaces.calICalendar;
const nsISupportsCString = Components.interfaces.nsISupportsCString;
const calIEvent = Components.interfaces.calIEvent;
const calIItemBase = Components.interfaces.calIItemBase;
const calITodo = Components.interfaces.calITodo;
const calEventClass = Components.classes["@mozilla.org/calendar/event;1"];
const kCalCalendarManagerContractID = "@mozilla.org/calendar/manager;1";
const kCalICalendarManager = Components.interfaces.calICalendarManager;
function makeOccurrence(item, start, end)
{
var occ = item.createProxy();
occ.recurrenceId = start;
occ.startDate = start;
occ.endDate = end;
return occ;
}
var activeCalendarManager = null;
function getCalendarManager()
{
if (!activeCalendarManager) {
activeCalendarManager =
Components.classes[kCalCalendarManagerContractID].getService(kCalICalendarManager);
}
return activeCalendarManager;
}
// END_OF_TIME needs to be the max value a PRTime can be
const START_OF_TIME = -0x7fffffffffffffff;
const END_OF_TIME = 0x7fffffffffffffff;
calDavCalendar.prototype = {
//
// nsISupports interface
//
QueryInterface: function (aIID) {
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(calICalendar) &&
!aIID.equals(Components.interfaces.nsIInterfaceRequestor)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
//
// nsICalendar interface
//
// attribute AUTF8String name;
get name() {
return getCalendarManager().getCalendarPref(this, "NAME");
},
set name(name) {
getCalendarManager().setCalendarPref(this, "NAME", name);
},
// readonly attribute AUTF8String type;
get type() { return "caldav"; },
mReadOnly: false,
get readOnly() {
return this.mReadOnly;
},
set readOnly(bool) {
this.mReadOnly = bool;
},
get canRefresh() {
// refresh() is currently not implemented, but we may want to change that
return false;
},
// attribute nsIURI uri;
mUri: null,
get uri() { return this.mUri; },
set uri(aUri) { this.mUri = aUri; },
get mCalendarUri() {
calUri = this.mUri.clone();
if (calUri.spec.charAt(calUri.spec.length-1) != '/') {
calUri.spec += "/";
}
return calUri;
},
refresh: function() {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
// attribute boolean suppressAlarms;
get suppressAlarms() { return false; },
set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
// void addObserver( in calIObserver observer );
addObserver: function (aObserver, aItemFilter) {
for each (obs in aObserver) {
if (obs == aObserver) {
return;
}
}
this.mObservers.push(aObserver);
},
// void removeObserver( in calIObserver observer );
removeObserver: function (aObserver) {
this.mObservers = this.mObservers.filter(
function(o) {return (o != aObserver);});
},
// void addItem( in calIItemBase aItem, in calIOperationListener aListener );
addItem: function (aItem, aListener) {
var newItem = aItem.clone();
return this.adoptItem(newItem, aListener);
},
// void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
adoptItem: function (aItem, aListener) {
if (this.readOnly) {
throw Components.interfaces.calIErrors.CAL_IS_READONLY;
}
if (aItem.id == null && aItem.isMutable)
// XXX real UUID here!!
aItem.id = "uuid" + (new Date()).getTime();
if (aItem.id == null) {
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.ADD,
aItem.id,
"Can't set ID on non-mutable item to addItem");
return;
}
// XXX how are we REALLY supposed to figure this out?
var locationPath = aItem.id + ".ics";
var itemUri = this.mCalendarUri.clone();
itemUri.spec = itemUri.spec + locationPath;
debug("itemUri.spec = " + itemUri.spec + "\n");
var eventResource = new WebDavResource(itemUri);
var listener = new WebDavListener();
var thisCalendar = this;
listener.onOperationComplete =
function onPutComplete(aStatusCode, aResource, aOperation, aClosure) {
// 201 = HTTP "Created"
//
if (aStatusCode == 201) {
debug("Item added successfully\n");
var retVal = Components.results.NS_OK;
} else if (aStatusCode == 200) {
// XXXdmose once we get etag stuff working, this should
//
debug("200 received from clients, until we have etags working"
+ " this probably means a collision; after that it'll"
+ " mean server malfunction/n");
retVal = Components.results.NS_ERROR_FAILURE;
} else {
if (aStatusCode > 999) {
aStatusCode = "0x" + aStatusCode.toString(16);
}
// XXX real error handling
debug("Error adding item: " + aStatusCode + "\n");
retVal = Components.results.NS_ERROR_FAILURE;
}
// notify the listener
if (aListener) {
try {
aListener.onOperationComplete(thisCalendar,
retVal,
aListener.ADD,
aItem.id,
aItem);
} catch (ex) {
debug("addItem's onOperationComplete threw an exception "
+ ex + "; ignoring\n");
}
}
// notify observers
if (Components.isSuccessCode(retVal)) {
thisCalendar.observeAddItem(aItem);
}
}
aItem.calendar = this;
aItem.generation = 1;
aItem.setProperty("X-MOZ-LOCATIONPATH", locationPath);
aItem.makeImmutable();
debug("icalString = " + aItem.icalString + "\n");
// XXX use if not exists
// do WebDAV put
var webSvc = Components.classes['@mozilla.org/webdav/service;1']
.getService(Components.interfaces.nsIWebDAVService);
webSvc.putFromString(eventResource, "text/calendar",
aItem.icalString, listener, this, null);
return;
},
// void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
modifyItem: function modifyItem(aNewItem, aOldItem, aListener) {
if (this.readOnly) {
throw Components.interfaces.calIErrors.CAL_IS_READONLY;
}
if (aNewItem.id == null) {
// XXXYYY fix to match iface spec
// this is definitely an error
if (aListener) {
try {
aListener.onOperationComplete(this,
Components.results.NS_ERROR_FAILURE,
aListener.MODIFY,
aItem.id,
"ID for modifyItem doesn't exist or is null");
} catch (ex) {
debug("modifyItem's onOperationComplete threw an"
+ " exception " + ex + "; ignoring\n");
}
}
return;
}
var eventUri = this.mCalendarUri.clone();
try {
eventUri.spec = this.mCalendarUri.spec + aNewItem.getProperty("X-MOZ-LOCATIONPATH");
debug("using X-MOZ-LOCATIONPATH: " + eventUri.spec + "\n");
} catch (ex) {
// XXX how are we REALLY supposed to figure this out?
eventUri.spec = eventUri.spec + aNewItem.id + ".ics";
}
var eventResource = new WebDavResource(eventUri);
var listener = new WebDavListener();
var thisCalendar = this;
listener.onOperationComplete = function(aStatusCode, aResource,
aOperation, aClosure) {
// 201 = HTTP "Created"
// 204 = HTTP "No Content"
//
if (aStatusCode == 204 || aStatusCode == 201) {
debug("Item modified successfully.\n");
var retVal = Components.results.NS_OK;
} else {
if (aStatusCode > 999) {
aStatusCode = "0x " + aStatusCode.toString(16);
}
debug("Error modifying item: " + aStatusCode + "\n");
// XXX deal with non-existent item here, other
// real error handling
// XXX aStatusCode will be 201 Created for a PUT on an item
// that didn't exist before.
retVal = Components.results.NS_ERROR_FAILURE;
}
// XXX ensure immutable version returned
// notify listener
if (aListener) {
try {
aListener.onOperationComplete(thisCalendar, retVal,
aListener.MODIFY,
aNewItem.id, aNewItem);
} catch (ex) {
debug("modifyItem's onOperationComplete threw an"
+ " exception " + ex + "; ignoring\n");
}
}
// notify observers
if (Components.isSuccessCode(retVal)) {
thisCalendar.observeModifyItem(aNewItem, aOldItem);
}
return;
}
// XXX use if-exists stuff here
// XXX use etag as generation
// do WebDAV put
debug("modifyItem: aNewItem.icalString = " + aNewItem.icalString
+ "\n");
var webSvc = Components.classes['@mozilla.org/webdav/service;1']
.getService(Components.interfaces.nsIWebDAVService);
webSvc.putFromString(eventResource, "text/calendar",
aNewItem.icalString, listener, this, null);
return;
},
// void deleteItem( in calIItemBase aItem, in calIOperationListener aListener );
deleteItem: function (aItem, aListener) {
if (this.readOnly) {
throw Components.interfaces.calIErrors.CAL_IS_READONLY;
}
if (aItem.id == null) {
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.DELETE,
aItem.id,
"ID doesn't exist for deleteItem");
return;
}
// XXX how are we REALLY supposed to figure this out?
var eventUri = this.mCalendarUri.clone();
eventUri.spec = eventUri.spec + aItem.id + ".ics";
var eventResource = new WebDavResource(eventUri);
var listener = new WebDavListener();
var thisCalendar = this;
listener.onOperationComplete =
function onOperationComplete(aStatusCode, aResource, aOperation,
aClosure) {
// 204 = HTTP "No content"
//
if (aStatusCode == 204) {
debug("Item deleted successfully.\n");
var retVal = Components.results.NS_OK;
} else {
debug("Error deleting item: " + aStatusCode + "\n");
// XXX real error handling here
retVal = Components.results.NS_ERROR_FAILURE;
}
// notify the listener
if (aListener) {
try {
aListener.onOperationComplete(thisCalendar,
Components.results.NS_OK,
aListener.DELETE,
aItem.id,
null);
} catch (ex) {
debug("deleteItem's onOperationComplete threw an"
+ " exception " + ex + "; ignoring\n");
}
}
// notify observers
if (Components.isSuccessCode(retVal)) {
thisCalendar.observeDeleteItem(aItem);
}
}
// XXX check etag/generation
// do WebDAV remove
var webSvc = Components.classes['@mozilla.org/webdav/service;1']
.getService(Components.interfaces.nsIWebDAVService);
webSvc.remove(eventResource, listener, this, null);
return;
},
// void getItem( in string id, in calIOperationListener aListener );
getItem: function (aId, aListener) {
if (!aListener)
return;
if (aId == null) {
aListener.onOperationComplete(this,
Components.results.NS_ERROR_FAILURE,
aListener.GET,
null,
"passed in empty iid");
return;
}
// 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";
var D = new Namespace("D", "DAV:");
queryXml =
<calendar-query xmlns:D="DAV:">
<D:prop>
<calendar-data/>
</D:prop>
<filter>
<comp-filter name="VCALENDAR">
<comp-filter name="VEVENT">
<prop-filter name="UID">
<text-match caseless="no">
{aId}
</text-match>
</prop-filter>
</comp-filter>
</comp-filter>
</filter>
</calendar-query>;
this.reportInternal(xmlHeader + queryXml.toXMLString(),
false, null, null, 1, aListener);
return;
},
reportInternal: function (aQuery, aOccurrences, aRangeStart, aRangeEnd, aCount, aListener)
{
var reportListener = new WebDavListener();
var count = 0; // maximum number of hits to return
var thisCalendar = this; // need to access from inside the callback
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?
// XXX if it's an error, it might be valid?
throw("XXX report result for calendar, not event\n");
}
var items = null;
// XXX need to do better than looking for just 200
if (aStatusCode == 200) {
// we've already called back the maximum number of hits, so
// we're done here.
//
if (aCount && count >= aCount) {
return;
}
++count;
// 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);
var response = xSerializer.serializeToString(aDetail);
var responseElement = new XML(response);
// 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");
var D = new Namespace("DAV:");
// create a local event item
// XXX not just events
var item = calEventClass.createInstance(calIEvent);
// cause returned data to be parsed into the event item
var calData = responseElement..C::["calendar-data"];
if (!calData.toString().length) {
Components.utils.reportError(
"Empty or non-existent <calendar-data> element returned" +
" by CalDAV server for URI <" + aResource.spec +
">; ignoring");
return;
}
debug("item result = \n" + calData + "\n");
// XXX try-catch
item.icalString = calData;
item.calendar = thisCalendar;
// save the location name in case we need to modify
var locationPath = decodeURIComponent(aResource.path)
.substr(thisCalendar.mCalendarUri.path.length);
item.setProperty("X-MOZ-LOCATIONPATH", locationPath);
debug("X-MOZ-LOCATIONPATH = " + locationPath + "\n");
item.makeImmutable();
// figure out what type of item to return
var iid;
if(aOccurrences) {
iid = calIItemBase;
if (item.recurrenceInfo) {
debug("ITEM has recurrence: " + item + " (" + item.title + ")\n");
debug("rangestart: " + aRangeStart.jsDate + " -> " + aRangeEnd.jsDate + "\n");
// XXX does getOcc call makeImmutable?
items = item.recurrenceInfo.getOccurrences(aRangeStart,
aRangeEnd,
0, {});
} else {
// XXX need to make occurrences immutable?
items = [ item ];
}
rv = Components.results.NS_OK;
} else if (item.QueryInterface(calIEvent)) {
iid = calIEvent;
rv = Components.results.NS_OK;
items = [ item ];
} else if (item.QueryInterface(calITodo)) {
iid = calITodo;
rv = Components.results.NS_OK;
items = [ item ];
} else {
errString = "Can't deduce item type based on query";
rv = Components.results.NS_ERROR_FAILURE;
}
} else {
// XXX
debug("aStatusCode = " + aStatusCode + "\n");
errString = "XXX";
rv = Components.results.NS_ERROR_FAILURE;
}
// XXX handle aCount
if (errString) {
debug("errString = " + errString + "\n");
}
try {
aListener.onGetResult(thisCalendar, rv, iid, null,
items ? items.length : 0,
errString ? errString : items);
} catch (ex) {
debug("reportInternal's onGetResult threw an"
+ " exception " + ex + "; ignoring\n");
}
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
try {
if (aListener) {
aListener.onOperationComplete(thisCalendar,
Components.results.
NS_ERROR_FAILURE,
aListener.GET, null,
errString);
}
} catch (ex) {
debug("reportInternal's onOperationComplete threw an"
+ " exception " + ex + "; ignoring\n");
}
return;
};
// convert this into a form the WebDAV service can use
var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.getService(Components.interfaces.nsIDOMParser);
queryDoc = xParser.parseFromString(aQuery, "application/xml");
// construct the resource we want to search against
var calendarDirUri = this.mCalendarUri.clone();
debug("report uri = " + calendarDirUri.spec + "\n");
var calendarDirResource = new WebDavResource(calendarDirUri);
var webSvc = Components.classes['@mozilla.org/webdav/service;1']
.getService(Components.interfaces.nsIWebDAVService);
webSvc.report(calendarDirResource, queryDoc, true, reportListener,
this, 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)
{
if (!aListener)
return;
// this is our basic report xml
var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
var D = new Namespace("D", "DAV:");
default xml namespace = C;
var queryXml =
<calendar-query xmlns:D={D}>
<D:prop>
<calendar-data/>
</D:prop>
<filter>
<comp-filter name="VCALENDAR">
<comp-filter/>
</comp-filter>
</filter>
</calendar-query>;
// figure out
var compFilterNames = new Array();
compFilterNames[calICalendar.ITEM_FILTER_TYPE_TODO] = "VTODO";
compFilterNames[calICalendar.ITEM_FILTER_TYPE_VJOURNAL] = "VJOURNAL";
compFilterNames[calICalendar.ITEM_FILTER_TYPE_EVENT] = "VEVENT";
var filterTypes = 0;
for (var i in compFilterNames) {
if (aItemFilter & i) {
// XXX CalDAV only allows you to ask for one filter-type
// at once, so if the caller wants multiple filtern
// types, all they're going to get for now is events
// (since that's last in the Array). Sorry!
++filterTypes;
queryXml[0].C::filter.C::["comp-filter"]
.C::["comp-filter"] =
<comp-filter name={compFilterNames[i]}/>;
}
}
if (filterTypes < 1) {
debug("No item types specified\n");
// XXX should we just quietly call back the completion method?
throw NS_ERROR_FAILURE;
}
// if a time range has been specified, do the appropriate restriction.
// XXX express "end of time" in caldav by leaving off "start", "end"
if (aRangeStart && aRangeStart.isValid &&
aRangeEnd && aRangeEnd.isValid) {
var queryRangeStart = aRangeStart.clone();
var queryRangeEnd = aRangeEnd.clone();
queryRangeStart.isDate = false;
if (queryRangeEnd.isDate) {
// add a day to rangeEnd since we want to match events all that day
// and isDate=false is converting the date to midnight
queryRangeEnd.day++;
queryRangeEnd.normalize();
queryRangeEnd.isDate = false;
}
var rangeXml = <time-range start={queryRangeStart.getInTimezone("UTC").icalString}
end={queryRangeEnd.getInTimezone("UTC").icalString}/>;
// append the time-range as a child of our innermost comp-filter
queryXml[0].C::filter.C::["comp-filter"]
.C::["comp-filter"].appendChild(rangeXml);
}
var queryString = xmlHeader + queryXml.toXMLString();
debug("getItems(): querying CalDAV server for events: \n" +
queryString + "\n");
var occurrences = (aItemFilter &
calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0;
this.reportInternal(queryString, occurrences, aRangeStart, aRangeEnd,
aCount, aListener);
},
startBatch: function ()
{
this.observeBatchChange(true);
},
endBatch: function ()
{
this.observeBatchChange(false);
},
// nsIInterfaceRequestor impl
getInterface: function(iid) {
if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
// use the window watcher service to get a nsIAuthPrompt impl
return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.getNewAuthPrompter(null);
}
else if (iid.equals(Components.interfaces.nsIPrompt)) {
// use the window watcher service to get a nsIPrompt impl
return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.getNewPrompter(null);
}
throw Components.results.NS_ERROR_NO_INTERFACE;
},
//
// Helper functions
//
observeBatchChange: function observeBatchChange (aNewBatchMode) {
for each (obs in this.mObservers) {
if (aNewBatchMode) {
try {
obs.onStartBatch();
} catch (ex) {
debug("observer's onStartBatch threw an exception " + ex
+ "; ignoring\n");
}
} else {
try {
obs.onEndBatch();
} catch (ex) {
debug("observer's onEndBatch threw an exception " + ex
+ "; ignoring\n");
}
}
}
return;
},
observeAddItem:
function observeAddItem(aItem) {
for each (obs in this.mObservers) {
try {
obs.onAddItem(aItem);
} catch (ex) {
debug("observer's onAddItem threw an exception " + ex
+ "; ignoring\n");
}
}
return;
},
observeModifyItem:
function observeModifyItem(aNewItem, aOldItem) {
for each (obs in this.mObservers) {
try {
obs.onModifyItem(aNewItem, aOldItem);
} catch (ex) {
debug("observer's onModifyItem threw an exception " + ex
+ "; ignoring\n");
}
}
return;
},
observeDeleteItem:
function observeDeleteItem(aDeletedItem) {
for each (obs in this.mObservers) {
try {
obs.onDeleteItem(aDeletedItem);
} catch (ex) {
debug("observer's onDeleteItem threw an exception " + ex
+ "; ignoring\n");
}
}
return;
},
refresh: function calDAV_refresh() {
// XXX-fill this in, get a report for modifications+onModifyItem
}
};
function WebDavResource(url) {
this.mResourceURL = url;
}
WebDavResource.prototype = {
mResourceURL: {},
get resourceURL() {
return this.mResourceURL;} ,
QueryInterface: function(iid) {
if (iid.equals(CI.nsIWebDAVResource) ||
iid.equals(CI.nsISupports)) {
return this;
}
throw Components.interfaces.NS_NO_INTERFACE;
}
};
function WebDavListener() {
}
WebDavListener.prototype = {
QueryInterface: function (aIID) {
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(nsIWebDavOperationListener)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
onOperationComplete: function(aStatusCode, aResource, aOperation,
aClosure) {
// aClosure is the listener
aClosure.onOperationComplete(this, aStatusCode, 0, null, null);
debug("WebDavListener.onOperationComplete() called\n");
return;
},
onOperationDetail: function(aStatusCode, aResource, aOperation, aDetail,
aClosure) {
debug("WebDavListener.onOperationDetail() called\n");
return;
}
}
/****
**** module registration
****/
var calDavCalendarModule = {
mCID: Components.ID("{a35fc6ea-3d92-11d9-89f9-00045ace3b8d}"),
mContractID: "@mozilla.org/calendar/calendar;1?type=caldav",
registerSelf: function (compMgr, fileSpec, location, type) {
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.mCID,
"Calendar CalDAV back-end",
this.mContractID,
fileSpec,
location,
type);
},
getClassObject: function (compMgr, cid, iid) {
if (!cid.equals(this.mCID))
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
return this.mFactory;
},
mFactory: {
createInstance: function (outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new calDavCalendar()).QueryInterface(iid);
}
},
canUnload: function(compMgr) {
return true;
}
};
function NSGetModule(compMgr, fileSpec) {
return calDavCalendarModule;
}