diff --git a/calendar/locales/en-US/chrome/calendar/wcap.properties b/calendar/locales/en-US/chrome/calendar/wcap.properties index c87c94f0714..0893f19ebdb 100644 --- a/calendar/locales/en-US/chrome/calendar/wcap.properties +++ b/calendar/locales/en-US/chrome/calendar/wcap.properties @@ -45,10 +45,6 @@ noHttpsConfirmation.text=Insecure login on %1$S!\nContinue? noHttpsConfirmation.check.text=Don't ask again. noHttpsConfirmation.label=Warning! -# args: host -reconnectConfirmation.text=Session on %1$S has timed out. Reconnect? -reconnectConfirmation.label=Session TimeOut - # args: host, prodId, serverVersion, wcapVersion insufficientWcapVersionConfirmation.text=Server %1$S (%2$S, v%3$S, WCAP v%4$S) doesn't support a sufficient WCAP version! The required version is at least 3.0.0.\nContinue? insufficientWcapVersionConfirmation.label=Insufficient WCAP version! diff --git a/calendar/providers/wcap/Makefile.in b/calendar/providers/wcap/Makefile.in index 5ddd398086d..4e143242498 100644 --- a/calendar/providers/wcap/Makefile.in +++ b/calendar/providers/wcap/Makefile.in @@ -54,24 +54,12 @@ EXTRA_SCRIPTS = \ calWcapSession.js \ calWcapCalendarItems.js \ calWcapCalendar.js \ - calWcapCachedCalendar.js \ $(NULL) -EXTRA_PP_COMPONENTS = \ +EXTRA_COMPONENTS = \ calWcapCalendarModule.js \ $(NULL) -# minimum log level: -ifdef wcap_logging -ifeq (,$(findstring $(wcap_logging),1 2 3 4 5)) -DEFINES += -DLOG_LEVEL=2 -else -DEFINES += -DLOG_LEVEL=$(wcap_logging) -endif -else # ifdef wcap_logging -DEFINES += -DLOG_LEVEL=0 -endif # ifdef wcap_logging - # installing EXTRA_SCRIPTS into js/ # using NSINSTALL to make directory: no mtime to preserve: diff --git a/calendar/providers/wcap/calWcapCalendar.js b/calendar/providers/wcap/calWcapCalendar.js index 47dc2e2f208..2241fc9da76 100644 --- a/calendar/providers/wcap/calWcapCalendar.js +++ b/calendar/providers/wcap/calWcapCalendar.js @@ -37,73 +37,51 @@ * * ***** END LICENSE BLOCK ***** */ -function createWcapCalendar( calId, session, /*optional*/calProps ) +function createWcapCalendar(session, /*optional*/calProps) { - var cal = new calWcapCalendar( calId, session, calProps ); - switch (CACHE) { - case "memory": - case "storage": - // wrap it up: - var cal_ = new calWcapCachedCalendar(); - cal_.remoteCal = cal; - cal = cal_; - break; - } + var cal = new calWcapCalendar(session, calProps); +// switch (CACHE) { +// case "memory": +// case "storage": +// // wrap it up: +// var cal_ = new calWcapCachedCalendar(); +// cal_.remoteCal = cal; +// cal = cal_; +// break; +// } return cal; } -function calWcapCalendar( calId, session, /*optional*/calProps ) { +function calWcapCalendar(session, /*optional*/calProps) { this.wrappedJSObject = this; - this.m_calId = calId; this.m_session = session; this.m_calProps = calProps; this.m_bSuppressAlarms = SUPPRESS_ALARMS; - // init queued calls: - this.adoptItem = makeQueuedCall(this.session.asyncQueue, - this, this.adoptItem_queued); - this.modifyItem = makeQueuedCall(this.session.asyncQueue, - this, this.modifyItem_queued); - this.deleteItem = makeQueuedCall(this.session.asyncQueue, - this, this.deleteItem_queued); - this.getItem = makeQueuedCall(this.session.asyncQueue, - this, this.getItem_queued); - this.getItems = makeQueuedCall(this.session.asyncQueue, - this, this.getItems_queued); - this.syncChangesTo = makeQueuedCall(this.session.asyncQueue, - this, this.syncChangesTo_queued); - - if (LOG_LEVEL > 0 && this.m_calProps) { - if (this.m_calId != this.getCalendarProperties( - "X-NSCP-CALPROPS-RELATIVE-CALID", {})[0]) { - this.notifyError("calId mismatch: " + this.m_calId + - " vs. " + ar[0]); - } + if (this.m_calProps) { + var ar = this.getCalProps("X-NSCP-CALPROPS-RELATIVE-CALID"); + if (ar.length > 0) + this.m_calId = ar[0]; + else + this.notifyError("no X-NSCP-CALPROPS-RELATIVE-CALID!"); } } calWcapCalendar.prototype = { - m_ifaces: [ Components.interfaces.calIWcapCalendar, - Components.interfaces.calICalendar, + m_ifaces: [ calIWcapCalendar, + calICalendar, Components.interfaces.calICalendarProvider, Components.interfaces.nsIInterfaceRequestor, Components.interfaces.nsIClassInfo, Components.interfaces.nsISupports ], // nsISupports: - QueryInterface: - function( iid ) - { - for each ( var iface in this.m_ifaces ) { - if (iid.equals( iface )) - return this; - } - throw Components.results.NS_ERROR_NO_INTERFACE; + QueryInterface: function calWcapCalendar_QueryInterface(iid) { + qiface(this.m_ifaces, iid); + return this; }, // nsIClassInfo: - getInterfaces: - function( count ) - { + getInterfaces: function calWcapCalendar_getInterfaces( count ) { count.value = this.m_ifaces.length; return this.m_ifaces; }, @@ -116,14 +94,14 @@ calWcapCalendar.prototype = { get classID() { return calWcapCalendarModule.WcapCalendarInfo.classID; }, - getHelperForLanguage: function( language ) { return null; }, + getHelperForLanguage: + function calWcapCalendar_getHelperForLanguage(language) { return null; }, implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT, flags: 0, // nsIInterfaceRequestor: - getInterface: - function( iid, instance ) + getInterface: function calWcapCalendar_getInterface(iid, instance) { if (iid.equals(Components.interfaces.nsIAuthPrompt)) { // use the window watcher service to get a nsIAuthPrompt impl @@ -137,35 +115,22 @@ calWcapCalendar.prototype = { return null; }, - toString: - function() - { + toString: function calWcapCalendar_toString() { var str = this.session.toString(); - str += (", calId=" + - (this.session.isLoggedIn ? this.calId : this.m_calId)); + if (this.m_calId) + str += (", calId=" + this.calId); + else + str += ", default calendar"; return str; }, - log: - function( msg, context ) - { - return logMessage( context ? context : this.toString(), msg ); - }, - logError: - function( err, context ) - { - var msg = errorToString(err); - Components.utils.reportError( this.log("error: " + msg, context) ); - return msg; - }, - notifyError: - function( err ) + notifyError: function calWcapCalendar_notifyError(err) { debugger; - var msg = this.logError(err); + var msg = logError(err, this); this.notifyObservers( "onError", err instanceof Components.interfaces.nsIException - ? [err.result, err.message] : [-1, msg] ); + ? [err.result, err.message] : [isNaN(err) ? -1 : err, msg] ); }, // calICalendarProvider: @@ -173,19 +138,13 @@ calWcapCalendar.prototype = { return null; }, // displayName attribute already part of calIWcapCalendar - createCalendar: - function( name, url, listener ) - { + createCalendar: function calWcapCalendar_createCalendar(name, url, listener) { throw NS_ERROR_NOT_IMPLEMENTED; }, - deleteCalendar: - function( calendar, listener ) - { + deleteCalendar: function calWcapCalendar_deleteCalendar(calendar, listener) { throw NS_ERROR_NOT_IMPLEMENTED; }, - getCalendar: - function( url ) - { + getCalendar: function calWcapCalendar_getCalendar( url ) { throw NS_ERROR_NOT_IMPLEMENTED; }, @@ -213,23 +172,16 @@ calWcapCalendar.prototype = { // early, so don't log in here if not logged in already... !this.session.isLoggedIn || // limit to write permission on components: - !this.checkAccess( - Components.interfaces.calIWcapCalendar.AC_COMP_WRITE)); + !this.checkAccess(calIWcapCalendar.AC_COMP_WRITE)); }, - set readOnly( bReadOnly ) { this.m_bReadOnly = bReadOnly; }, - - resetCalProps: - function() - { - this.m_calProps = null; + set readOnly(bReadOnly) { + this.m_bReadOnly = bReadOnly; }, get uri() { if (this.m_calId) { var ret = this.session.uri.clone(); - if (LOG_LEVEL == 42) { // xxx todo: interims hack for tbe - ret.path += ("?calid=" + encodeURIComponent(this.m_calId)); - } + ret.path += ("?calid=" + encodeURIComponent(this.m_calId)); return ret; } else @@ -237,26 +189,25 @@ calWcapCalendar.prototype = { }, set uri( thatUri ) { this.session.uri = thatUri; - // uri changes, but calId stays constant: - this.resetCalProps(); }, - notifyObservers: - function( func, args ) { - this.session.notifyObservers( func, args ); + notifyObservers: function calWcapCalendar_notifyObservers(func, args) { + this.session.notifyObservers(func, args); }, - addObserver: - function( observer ) { - this.session.addObserver( observer ); + addObserver: function calWcapCalendar_addObserver(observer) { + this.session.addObserver(observer); }, - removeObserver: - function( observer ) { - this.session.removeObserver( observer ); + removeObserver: function calWcapCalendar_removeObserver(observer) { + this.session.removeObserver(observer); }, // xxx todo: batch currently not used - startBatch: function() { this.notifyObservers( "onStartBatch", [] ); }, - endBatch: function() { this.notifyObservers( "onEndBatch", [] ); }, + startBatch: function calWcapCalendar_startBatch() { + this.notifyObservers("onStartBatch", []); + }, + endBatch: function calWcapCalendar_endBatch() { + this.notifyObservers("onEndBatch", []); + }, // xxx todo: rework like in // https://bugzilla.mozilla.org/show_bug.cgi?id=257428 @@ -273,63 +224,58 @@ calWcapCalendar.prototype = { // xxx todo: assume alarms if not logged in already (this.session.isLoggedIn && !this.isOwnedCalendar)); }, - set suppressAlarms( bSuppressAlarms ) { + set suppressAlarms(bSuppressAlarms) { this.m_bSuppressAlarms = bSuppressAlarms; }, - get canRefresh() { return true; }, - refresh: - function() { - // xxx todo: somehow misusing reload remote calendars for - // session renewal... - // refresh session ticket immedidately, not queued: - this.log("refresh!"); - this.session.refresh(); + get canRefresh() { return false; }, + refresh: function calWcapCalendar_refresh() { + log("refresh.", this); }, - getCommandUrl: - function( wcapCommand ) + issueNetworkRequest: function calWcapCalendar_issueNetworkRequest( + request, respFunc, dataConvFunc, wcapCommand, params, accessRights) { - var url = this.session.getCommandUrl(wcapCommand); - var calId = this.calId; - if (calId) - url += ("&calid=" + encodeURIComponent(calId)); - return url; + var this_ = this; + // - bootstrap problem: no cal_props, no access check, no default calId + // - assure being logged in, thus the default cal_props are available + // - every subscribed calendar will come along with cal_props + return this.session.getSessionId( + request, + function getSessionId_resp(err, sessionId) { + try { + if (err) + throw err; + this_.assureAccess(accessRights); + params += ("&calid=" + encodeURIComponent(this_.calId)); + this_.session.issueNetworkRequest( + request, respFunc, dataConvFunc, wcapCommand, params); + } + catch (exc) { + respFunc(exc); + } + }); }, // calIWcapCalendar: m_session: null, get session() { - if (this.m_session) - return this.m_session; - throw new Components.Exception("Disconnected from session!"); + return this.m_session; }, - // xxx todo: for now to make subscriptions context menu work, - // will vanish when UI has been revised and every subscribed - // calendar has its own calICalendar object... - // poking calId, so default calendar will then behave - // like a subscribed one... m_calId: null, get calId() { if (this.m_calId) return this.m_calId; return this.session.defaultCalId; }, - set calId( id ) { - this.log( "setting calId to " + id ); - this.m_calId = id; - // refresh calprops: - this.getCalProps_( true /*async*/, true /*refresh*/ ); - }, get ownerId() { - var ar = this.getCalendarProperties("X-NSCP-CALPROPS-PRIMARY-OWNER",{}); + var ar = this.getCalProps("X-NSCP-CALPROPS-PRIMARY-OWNER"); if (ar.length == 0) { var calId = this.calId; - this.logError( - "cannot determine primary owner of calendar " + calId ); + logError("cannot determine primary owner of calendar " + calId, this); // fallback to calId prefix: var nColon = calId.indexOf(":"); if (nColon >= 0) @@ -340,7 +286,7 @@ calWcapCalendar.prototype = { }, get description() { - var ar = this.getCalendarProperties("X-NSCP-CALPROPS-DESCRIPTION", {}); + var ar = this.getCalProps("X-NSCP-CALPROPS-DESCRIPTION"); if (ar.length == 0) { // fallback to display name: return this.displayName; @@ -349,11 +295,10 @@ calWcapCalendar.prototype = { }, get displayName() { - var ar = this.getCalendarProperties("X-NSCP-CALPROPS-NAME", {}); + var ar = this.getCalProps("X-NSCP-CALPROPS-NAME"); if (ar.length == 0) { // fallback to common name: - ar = this.getCalendarProperties( - "X-S1CS-CALPROPS-COMMON-NAME", {}); + ar = this.getCalProps("X-S1CS-CALPROPS-COMMON-NAME"); if (ar.length == 0) { return this.calId; } @@ -362,69 +307,41 @@ calWcapCalendar.prototype = { }, get isOwnedCalendar() { + if (this.isDefaultCalendar) + return true; // default calendar is owned return (this.ownerId == this.session.userId); }, + get isDefaultCalendar() { + return !this.m_calId; + }, + getCalendarProperties: - function( propName, out_count ) - { - var ret = filterCalProps( - propName, this.getCalProps_(false /* !async: waits for response*/)); + function(propName, out_count) { + var ret = this.getCalProps(propName); out_count.value = ret.length; return ret; }, - m_calProps: null, - getCalProps_: - function( bAsync, bRefresh ) - { -// this.session.assureLoggedIn(); - if (bRefresh || !this.m_calProps) { - this.m_calProps = null; - var url = this.getCommandUrl( "get_calprops" ); - url += "&fmt-out=text%2Fxml"; - var this_ = this; - function resp( wcapResponse ) { - try { - // first statement, may throw: - var xml = wcapResponse.data; - if (this_.m_calProps == null) - this_.m_calProps = xml; - } - catch (exc) { - // just logging here, because user may have dangling - // users referred in his subscription list: - this_.logError(exc); - if (!bAsync && testResultCode( - exc, Components.interfaces. - calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR)) { - // the user obviously has no property access, - // forward in case of synchronous calls. - // async will be swallowed though. - throw exc; - } - } - } - if (bAsync) - this.session.issueAsyncRequest( url, stringToXml, resp ); - else - this.session.issueSyncRequest( url, stringToXml, resp ); + + getCalProps: function calWcapCalendar_getCalProps(propName) { + if (!this.m_calProps) { + log("soft error: no calprops, most possibly not logged in.", this); +// throw new Components.Exception("No calprops available!", +// Components.results.NS_ERROR_NOT_AVAILABLE); } - return this.m_calProps; + return filterXmlNodes(propName, this.m_calProps); }, get defaultTimezone() { - var tzid = this.getCalendarProperties("X-NSCP-CALPROPS-TZID", {}); + var tzid = this.getCalProps("X-NSCP-CALPROPS-TZID"); if (tzid.length == 0) { - this.logError("cannot get X-NSCP-CALPROPS-TZID!", - "defaultTimezone"); + logError("defaultTimezone: cannot get X-NSCP-CALPROPS-TZID!", this); return "UTC"; // fallback } return tzid[0]; }, - getAlignedTimezone: - function( tzid ) - { + getAlignedTimezone: function calWcapCalendar_getAlignedTimezone(tzid) { // check whether it is one of cs: if (tzid.indexOf("/mozilla.org/") == 0) { // cut mozilla prefix: assuming that the latter string portion @@ -438,55 +355,51 @@ calWcapCalendar.prototype = { // user's default if not supported directly var ret = this.defaultTimezone; // use calendar's default: - this.log(tzid + " not supported, falling back to default: " + ret); + log(tzid + " not supported, falling back to default: " + ret, this); return ret; } else // is ok (supported): return tzid; }, - checkAccess: - function( accessControlBits ) + checkAccess: function calWcapCalendar_checkAccess(accessControlBits) { // xxx todo: take real acl into account // for now, assuming that owners have been granted full access, // and all others can read, but not add/modify/delete. - var granted = Components.interfaces.calIWcapCalendar.AC_FULL; + var granted = calIWcapCalendar.AC_FULL; if (!this.isOwnedCalendar) { // burn out write access: - granted &= ~(Components.interfaces.calIWcapCalendar.AC_COMP_WRITE | - Components.interfaces.calIWcapCalendar.AC_PROP_WRITE); + granted &= ~(calIWcapCalendar.AC_COMP_WRITE | + calIWcapCalendar.AC_PROP_WRITE); } // check whether every bit fits: return ((accessControlBits & granted) == accessControlBits); }, - assureAccess: - function( accessControlBits ) + assureAccess: function calWcapCalendar_assureAccess(accessControlBits) { if (!this.checkAccess(accessControlBits)) { throw new Components.Exception("Access denied!", - Components.interfaces.calIWcapErrors - .WCAP_ACCESS_DENIED_TO_CALENDAR); + calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR); // xxx todo: throwing different error here, no // calIErrors.CAL_IS_READONLY anymore } }, - defineAccessControl: - function( userId, accessControlBits ) + defineAccessControl: function calWcapCalendar_defineAccessControl( + userId, accessControlBits) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, - resetAccessControl: - function( userId ) + resetAccessControl: function calWcapCalendar_resetAccessControl(userId) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, - getAccessControlDefinitions: - function( out_count, out_users, out_accessControlBits ) + getAccessControlDefinitions: function calWcapCalendar_getAccessControlDefinitions( + out_count, out_users, out_accessControlBits) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; } diff --git a/calendar/providers/wcap/calWcapCalendarItems.js b/calendar/providers/wcap/calWcapCalendarItems.js index 8eb26b3e233..cdec228c2a1 100644 --- a/calendar/providers/wcap/calWcapCalendarItems.js +++ b/calendar/providers/wcap/calWcapCalendarItems.js @@ -37,41 +37,42 @@ * * ***** END LICENSE BLOCK ***** */ -// xxx todo: just to avoid registration errors, how to do better? var calWcapCalendar; if (!calWcapCalendar) { calWcapCalendar = {}; calWcapCalendar.prototype = {}; } -calWcapCalendar.prototype.encodeAttendee = function( - /* calIAttendee */ attendee, forceRSVP ) +calWcapCalendar.prototype.encodeAttendee = +function calWcapCalendar_encodeAttendee(att) { - this.log( "\tattendee.icalProperty.icalString=" + - attendee.icalProperty.icalString + - "\n\tattendee.id=" + attendee.id + - "\n\tattendee.commonName=" + attendee.commonName + - "\n\tattendee.role=" + attendee.role + - "\n\tattendee.participationStatus=" + - attendee.participationStatus ); - var ret = ("RSVP=" + (forceRSVP || attendee.rsvp ? "TRUE" : "FALSE")); - if (attendee.participationStatus != null) - ret += ("^PARTSTAT=" + attendee.participationStatus); - if (attendee.role != null) - ret += ("^ROLE=" + attendee.role); - if (attendee.commonName != null) - ret += ("^CN=" + encodeURIComponent(attendee.commonName)); - ret += ("^" + encodeURIComponent(attendee.id)); - return ret; + if (LOG_LEVEL > 2) + log("attendee.icalProperty.icalString=" + att.icalProperty.icalString, this); + function encodeAttr(val, attr, params) { + if (val && val.length > 0) { + if (params.length > 0) + params += "^"; + if (attr) + params += (attr + "="); + params += val; + } + return params; + } + var params = "";//encodeAttr(att.rsvp, "RSVP", ""); + params = encodeAttr(att.participationStatus, "PARTSTAT", params); + params = encodeAttr(att.role, "ROLE", params); + params = encodeAttr(att.commonName, "CN", params); + return encodeAttr(att.id, null, params); }; -calWcapCalendar.prototype.encodeRecurrenceParams = function( item, oldItem ) +calWcapCalendar.prototype.encodeRecurrenceParams = +function calWcapCalendar_encodeRecurrenceParams(item, oldItem) { var rrules = {}; var rdates = {}; var exrules = {}; var exdates = {}; - this.getRecurrenceParams( item, rrules, rdates, exrules, exdates ); + this.getRecurrenceParams(item, rrules, rdates, exrules, exdates); if (oldItem) { // actually only write changes if an old item has been changed, because // cs recreates the whole series if a rule has changed. @@ -112,7 +113,7 @@ calWcapCalendar.prototype.encodeRecurrenceParams = function( item, oldItem ) exdates.value = null; // don't write } - function encodeList( list ) { + function encodeList(list) { var ret = ""; for each ( var str in list ) { if (ret.length > 0) @@ -138,21 +139,21 @@ calWcapCalendar.prototype.encodeRecurrenceParams = function( item, oldItem ) // if rchange=0 is set! }; -calWcapCalendar.prototype.getRecurrenceParams = function( - item, out_rrules, out_rdates, out_exrules, out_exdates ) +calWcapCalendar.prototype.getRecurrenceParams = +function calWcapCalendar_getRecurrenceParams( + item, out_rrules, out_rdates, out_exrules, out_exdates) { // recurrences: out_rrules.value = []; out_rdates.value = []; out_exrules.value = []; out_exdates.value = []; - if (item.recurrenceInfo != null) { - var rItems = item.recurrenceInfo.getRecurrenceItems( {} ); + if (item.recurrenceInfo) { + var rItems = item.recurrenceInfo.getRecurrenceItems({}); for each ( var rItem in rItems ) { var isNeg = rItem.isNegative; // xxx todo: need to QueryInterface() here? - if (rItem instanceof Components.interfaces.calIRecurrenceRule) - { + if (rItem instanceof Components.interfaces.calIRecurrenceRule) { var rule = ("\"" + encodeURIComponent( rItem.icalProperty.valueAsIcalString) + "\""); @@ -162,9 +163,7 @@ calWcapCalendar.prototype.getRecurrenceParams = function( out_rrules.value.push( rule ); } // xxx todo: need to QueryInterface() here? - else if ( - rItem instanceof Components.interfaces.calIRecurrenceDateSet) - { + else if (rItem instanceof Components.interfaces.calIRecurrenceDateSet) { var d = rItem.getDates({}); for each ( var d in rdates ) { if (isNeg) @@ -174,627 +173,530 @@ calWcapCalendar.prototype.getRecurrenceParams = function( } } // xxx todo: need to QueryInterface() here? - else if ( - rItem instanceof Components.interfaces.calIRecurrenceDate) - { + else if (rItem instanceof Components.interfaces.calIRecurrenceDate) { if (isNeg) out_exdates.value.push( getIcalUTC(rItem.date) ); else out_rdates.value.push( getIcalUTC(rItem.date) ); } else { - this.notifyError( - "don\'t know how t handle this recurrence item: " + - rItem.valueAsIcalString ); + this.notifyError("don\'t know how to handle this recurrence item: " + + rItem.valueAsIcalString); } } } }; -calWcapCalendar.prototype.storeItem = function( item, oldItem, receiverFunc ) +// why ever, X-S1CS-EMAIL is unsupported though documented +// for get_calprops... WTF. +function getCalId(att) { + return (att ? att.getProperty("X-S1CS-CALID") : null); +} + +function getAttendeeByCalId(atts, calId) { + for each (var att in atts) { + if (getCalId(att) == calId) + return att; + } + return null; +} + +calWcapCalendar.prototype.isInvitation = +function calWcapCalendar_isInvitation(item) { + var ownerId = this.ownerId; + var orgUID = getCalId(item.organizer); + if (!orgUID) + return false; + // xxx todo: we assume calid globally unique. If X-S1CS-CALID is + // not available, we assume an invitation => REPLY + if (orgUID && orgUID == ownerId) + return false; + return (getAttendeeByCalId(item.getAttendees({}), ownerId) != null); +}; + +calWcapCalendar.prototype.getInvitedAttendee = +function calWcapCalendar_getInvitedAttendee(item) +{ + return getAttendeeByCalId(item.getAttendees({}), this.ownerId); +}; + +function equalDatetimes(one, two) { + return ((!one && !two) || (one && two && one.compare(two) == 0)); +} + +function diffProperty(newItem, oldItem, propName) { + var val = null; + if (newItem.hasProperty(propName)) + val = newItem.getProperty(propName); + if (oldItem && oldItem.hasProperty(propName)) { + if (!val) // property has been deleted + val = ""; + else if (val == oldItem.getProperty(propName)) + val = null; + } + return val; +} + +calWcapCalendar.prototype.storeItem = +function calWcapCalendar_storeItem(bAddItem, item, oldItem, request, netRespFunc) +{ + var this_ = this; var bIsEvent = isEvent(item); var bIsParent = isParent(item); - var url = this.getCommandUrl( bIsEvent ? "storeevents" : "storetodos" ); - - if (oldItem) { // modifying - url += ("&uid=" + encodeURIComponent(item.id)); - if (bIsParent) { - // (WCAP_STORE_TYPE_MODIFY) error if not existing: - url += "&storetype=2"; - url += "&mod=4"; // THIS AND ALL INSTANCES - } - else { // modifying occurences lands here: occurence may not exist - url += "&mod=1"; // THIS INSTANCE - // if not set, whole series of events is modified: - url += ("&rid=" + getIcalUTC(item.recurrenceId)); - } - } - else { // adding - // (WCAP_STORE_TYPE_CREATE) error if existing item: - url += "&storetype=1"; - } - - url += "&fetch=1&relativealarm=1&compressed=1&recurring=1"; - url += "&replace=1"; // (update) don't append to any lists - url += "&fmt-out=text%2Fcalendar"; - - // xxx todo: alarm mimic of todos currently different: - // WCAP offsets relate to DUE - if (bIsEvent) { - - // alarm support: - var alarmStart = item.alarmOffset; - if (alarmStart) { - var alarmRelated = item.alarmRelated; - if (alarmRelated==Components.interfaces.calIItemBase.ALARM_RELATED_END){ - // cs does not support explicit RELATED=END when - // both dtstart/due are written - var dur = item.duration; - if (dur) { // dtstart+due given - alarmStart = alarmStart.clone(); - dur = dur.clone(); - dur.isNegative = !dur.isNegative; - alarmStart.addDuration(dur); - } // else only dtend is set - } - - var emails = ""; - if (item.hasProperty("alarmEmailAddress")) - emails = encodeURIComponent(item.getProperty("alarmEmailAddress")); - else { - // minimal alarm server support: - // Alarms are currently off by default, - // so let server at least send reminder eMails... - this.session.getDefaultAlarmEmails({}).forEach( - function(email) { - if (emails.length > 0) - emails += ";"; - emails += encodeURIComponent(email); - } ); - } - url += ("&alarmStart=" + alarmStart.icalString); - url += ("&alarmEmails=" + emails); - url += "&alarmPopup="; - if (emails.length == 0) - url += alarmStart.icalString; - // xxx todo: missing: alarm triggers for flashing, etc. - } - else { - // clear popup, emails: - url += "&alarmStart=&alarmPopup=&alarmEmails="; - } - - } // if (bIsEvent) - - var ownerId = this.ownerId; - var orgUID = ((item.organizer && item.organizer.id) - ? item.organizer.id : ownerId); - + var bAttendeeReply = false; var bOrgRequest = false; - - // attendees: - var attendees = item.getAttendees({}); - if (attendees.length > 0) { - // ORGANIZER is owner fo this cal? - if (!oldItem || (orgUID == ownerId && - // xxx todo: - // we assume that the alarm service writes a new - // lastAck here (only); then we update only - // the organizer's copy of the item, no REQUEST: - getIcalUTC(oldItem.alarmLastAck) == - getIcalUTC(item.alarmLastAck))) - { - bOrgRequest = true; - url += "&method=2"; // REQUEST - url += ("&orgUID=" + encodeURIComponent(orgUID)); - url += "&attendees="; - for ( var i = 0; i < attendees.length; ++i ) { - if (i > 0) - url += ";"; - url += this.encodeAttendee( attendees[i], true /*forceRSVP*/ ); - } - } - else { // in modifyItem(), attendee's calendar: - var attendee = item.getAttendeeById(ownerId); - if (!attendee) - return false; - this.log( "attendee: " + attendee.icalProperty.icalString ); - // REPLY first for just this calendar owner: - url += "&method=4"; -// var oldAttendee = oldItem.getAttendeeById(ownerId); -// if (!oldAttendee || -// attendee.participationStatus != oldAttendee.participationStatus) -// { - url += ("&attendees=PARTSTAT=" + attendee.participationStatus); - url += ("^" + ownerId); -// } -// else { -// function getAlarmStart(item) { -// if (!item) -// return null; -// if (!item.alarmStart) -// return ""; -// return item.alarmStart.icalString; -// } -// if (getAlarmStart(item) == getAlarmStart(oldItem)) -// return false; // no changes need to be written -// } - this.session.issueAsyncRequest(url, stringToIcal, receiverFunc); - return true; - } - } - else - url += "&orgUID=&attendees="; // using just PUBLISH (method=1) + var params = ""; - // xxx todo: default prio is 0 (5 in sjs cs) - // save PRIORITY only if actually set, else delete: - url += "&priority="; - if (item.hasProperty("PRIORITY")) - url += encodeURIComponent( item.getProperty("PRIORITY") ); - - var icsClass = ((item.privacy != null && item.privacy != "") - ? item.privacy : "PUBLIC"); - url += ("&icsClass="+ icsClass); - - if (!bIsEvent && item.isCompleted) { - url += "&status=4"; // force to COMPLETED - } - else { - switch (item.status) { - case "CONFIRMED": url += "&status=0"; break; - case "CANCELLED": url += "&status=1"; break; - case "TENTATIVE": url += "&status=2"; break; - case "NEEDS-ACTION": url += "&status=3"; break; - case "COMPLETED": url += "&status=4"; break; - case "IN-PROCESS": url += "&status=5"; break; - case "DRAFT": url += "&status=6"; break; - case "FINAL": url += "&status=7"; break; - default: - url += "&status=3"; // NEEDS-ACTION -// this.logError( "storeItem(): unexpected item status=" + -// item.status ); - break; - } - } - - // attachment urls: - url += "&attachments="; - var attachments = item.attachments; - if (attachments != null) { - for ( var i = 0; i < attachments.length; ++i ) { - if (i > 0) - url += ";"; - var obj = attachments[i]; - if (obj instanceof String) { - url += encodeURIComponent(obj); - } - else { - this.notifyError( - "only URLs supported as attachment, not: " + obj ); + var ownerId = this.ownerId; + if (oldItem && this.isInvitation(oldItem)) { // REPLY + bAttendeeReply = true; + var att = getAttendeeByCalId(item.getAttendees({}), ownerId); + if (att) { + log("attendee: " + att.icalProperty.icalString, this); + var oldAtt = getAttendeeByCalId(oldItem.getAttendees({}), ownerId); + if (att.participationStatus != oldAtt.participationStatus) { + // REPLY first for just this calendar owner: + params += ("&attendees=PARTSTAT=" + att.participationStatus + + "^" + ownerId); } } } - - // categories: - // xxx todo: check whether ;-separated: - url += "&categories="; - if (item.hasProperty( "CATEGORIES" )) { - url += encodeURIComponent( - item.getProperty( "CATEGORIES" ).replace(/,/g, ";") ); - } - - // xxx todo: missing relatedTos= in cal api - - url += ("&summary=" + encodeURIComponent(item.title)); - // desc: xxx todo attribute "description" not impl in calItemBase.js - url += "&desc="; - if (item.hasProperty( "DESCRIPTION" )) { - url += encodeURIComponent( item.getProperty( "DESCRIPTION" ) ); - } - // location: xxx todo currently not impl in calItemBase.js - url += "&location="; - if (item.hasProperty( "LOCATION" )) { - url += encodeURIComponent( item.getProperty( "LOCATION" ) ); - } - url += "&icsUrl="; - if (item.hasProperty( "URL" )) { - url += encodeURIComponent( item.getProperty( "URL" ) ); - } - - var dtstart = null; - var dtend = null; - var bIsAllDay = false; - - if (bIsEvent) { - dtstart = item.startDate; - var dtend = item.endDate; - url += ("&dtend=" + getIcalUTC(dtend)); - url += ("&X-NSCP-DTEND-TZID=" + - "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + - encodeURIComponent(this.getAlignedTimezone(dtend.timezone))); - this.log("dtstart=" + dtstart + "\ndtend=" + dtend, item.id); - bIsAllDay = (dtstart.isDate && dtend.isDate); - } - else { // calITodo: - // xxx todo: dtstart is mandatory for cs, so if this is - // undefined, assume an allDay todo??? - dtstart = item.entryDate; - dtend = item.dueDate; - url += ("&due=" + getIcalUTC(dtend)); - if (dtend) { - url += ("&X-NSCP-DUE-TZID=" + - "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + - encodeURIComponent( - this.getAlignedTimezone(dtend.timezone))); + else { // PUBLISH, REQUEST + if (bIsParent) { + var recParams = this.encodeRecurrenceParams(item, oldItem); + if (recParams.length > 0) { + // workaround server bug: if first occurrence is an exception + // and an EXDATE for that occurrence ought to be written, + // then the master item is replaced with that EXDATEd exception. WTF. + // therefore write whole master: + oldItem = null; + params += recParams; + } } - bIsAllDay = (dtstart && dtstart.isDate); - if (item.isCompleted) - url += "&percent=100"; - else - url += ("&percent=" + - (item.percentComplete ? item.percentComplete : "0")); - url += "&completed="; - if (item.completedDate != null) - url += getIcalUTC(item.completedDate); - else if (item.isCompleted) - url += getIcalUTC(getTime()); // repair missing completedDate - else - url += "0"; // not yet completed - // xxx todo: sentBy sentUID fields in cs: missing in cal api - } - - var strTransp = null; - if (item.hasProperty("TRANSP")) - strTransp = item.getProperty("TRANSP"); - switch (strTransp) { - case "TRANSPARENT": - url += "&transparent=1"; - break; - case "OPAQUE": - url += "&transparent=0"; - break; - default: - url += ("&transparent=" + - (((icsClass == "PRIVATE") || bIsAllDay) ? "1" : "0")); - break; - } - - url += ("&isAllDay=" + (bIsAllDay ? "1" : "0")); - - url += ("&dtstart=" + getIcalUTC(dtstart)); - if (dtstart) { - // important to provide tz info with entry date for proper - // occurrence calculation (daylight savings): - url += ("&X-NSCP-DTSTART-TZID=" + - "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + - encodeURIComponent(this.getAlignedTimezone(dtstart.timezone))); - // xxx todo: still needed? - url += ("&tzid=" + encodeURIComponent( - this.getAlignedTimezone(dtstart.timezone))); - } - - // xxx todo: however, sometimes socs just returns an empty calendar when - // nothing or only optional props like attendee ROLE has changed, - // although fetch=1. - // This also occurs when the event organizer is in the attendees - // list and switches its PARTSTAT too often... - // - // hack #1: ever changing X-dummy prop, then the cs engine seems to write - // every time. => does not work for recurring items, - // operation REPLACE does not work, just adds - // more and more props. WTF. - - // storing X-props does not work properly for - // recurring items or single occurrences - - // misusing CONTACTS for now to store additional X- data - // (not used in cs web-frontend nor in mozilla, but in Outlook...) - - // stamp[:lastack] - var contacts = getIcalUTC(getTime()); - if (bIsParent && !bOrgRequest) { - var lastAck = item.alarmLastAck; - if (lastAck) { - contacts += ":"; - contacts += getIcalUTC(lastAck); + var attendees = item.getAttendees({}); + if (attendees.length > 0) { + // xxx todo: we assume calid globally unique. If X-S1CS-CALID is + // not available, we assume a REPLY + // why ever, X-S1CS-EMAIL is unsupported though documented + // for get_calprops... WTF. + bOrgRequest = true; + function encodeAttendees(atts) { + function stringSort(one, two) { + if (one == two) + return 0; + return (one < two ? -1 : 1); + } + atts = atts.concat([]); + atts.sort(stringSort); + var ret = ""; + for (var i = 0; i < atts.length; ++i) { + if (ret.length > 0) + ret += ";"; + ret += this_.encodeAttendee(atts[i]); + } + return ret; + } + var attParam = encodeAttendees(attendees); + if (!oldItem || attParam != encodeAttendees(oldItem.getAttendees({}))) { + params += ("&orgUID=" + encodeURIComponent(ownerId)); + params += ("&attendees=" + attParam); + } } + // else using just PUBLISH (method=1) + else if (oldItem && oldItem.getAttendees({}).length > 0) { + params += "&attendees="; // clear attendees + } + + var val = item.title; + if (!oldItem || val != oldItem.title) + params += ("&summary=" + encodeURIComponent(val)); + // xxx todo: missing relatedTos= in cal api + val = diffProperty(item, oldItem, "CATEGORIES"); + if (val) // xxx todo: check whether ;-separated: + params += ("&categories=" + encodeURIComponent( val.replace(/,/g, ";") )); + // desc: xxx todo attribute "description" not impl in calItemBase.js + val = diffProperty(item, oldItem, "DESCRIPTION"); + if (val) + params += ("&desc=" + encodeURIComponent(val)); + // location: xxx todo currently not impl in calItemBase.js + val = diffProperty(item, oldItem, "LOCATION"); + if (val) + params += ("&location=" + encodeURIComponent(val)); + // xxx todo: default prio is 0 (5 in sjs cs) + val = diffProperty(item, oldItem, "PRIORITY"); + if (val) + params += ("&priority=" + encodeURIComponent(val)); + val = diffProperty(item, oldItem, "URL"); + if (val) + params += ("&icsUrl=" + encodeURIComponent(val)); + + function getPrivacy(item) { + return ((item.privacy && item.privacy != "") ? item.privacy : "PUBLIC"); + } + var icsClass = getPrivacy(item); + if (!oldItem || icsClass != getPrivacy(oldItem)) + params += ("&icsClass="+ icsClass); + + if (!oldItem || item.status != oldItem.status) { + switch (item.status) { + case "CONFIRMED": params += "&status=0"; break; + case "CANCELLED": params += "&status=1"; break; + case "TENTATIVE": params += "&status=2"; break; + case "NEEDS-ACTION": params += "&status=3"; break; + case "COMPLETED": params += "&status=4"; break; + case "IN-PROCESS": params += "&status=5"; break; + case "DRAFT": params += "&status=6"; break; + case "FINAL": params += "&status=7"; break; + default: + params += "&status=3"; // NEEDS-ACTION + break; + } + } + + // attachment urls: + function getAttachments(item) { + var ret = ""; + var attachments = item.attachments; + if (attachments) { + var strings = []; + for each (var att in attachements) { + if (typeof(att) == "string") + strings.push(encodeURIComponent(att)); + else + logError("only URLs supported as attachment, not: " + att, this_); + } + strings.sort(); + for (var i = 0; i < strings.length; ++i) { + if (i > 0) + ret += ";"; + ret += strings[i]; + } + } + return ret; + } + var val = getAttachments(item); + if (!oldItem || val != getAttachments(oldItem)) + params += ("&attachments=" + val); + + var dtstart = null; + var dtend = null; + var bIsAllDay = false; + + if (bIsEvent) { + dtstart = item.startDate; + var dtend = item.endDate; + bIsAllDay = (dtstart.isDate && dtend.isDate); + if (!oldItem || !equalDatetimes(dtstart, oldItem.startDate) || + !equalDatetimes(dtend, oldItem.endDate)) { + params += ("&dtstart=" + getIcalUTC(dtstart)); + params += ("&X-NSCP-DTSTART-TZID=" + + "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + + encodeURIComponent(this.getAlignedTimezone(dtstart.timezone))); + params += ("&dtend=" + getIcalUTC(dtend)); + params += ("&X-NSCP-DTEND-TZID=" + + "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + + encodeURIComponent(this.getAlignedTimezone(dtend.timezone))); + params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0"); +// // xxx todo: still needed? +// params += ("&tzid=" + encodeURIComponent( +// this.getAlignedTimezone(dtstart.timezone))); + } + } + else { // calITodo: + // xxx todo: dtstart is mandatory for cs, so if this is + // undefined, assume an allDay todo??? + dtstart = item.entryDate; + dtend = item.dueDate; + bIsAllDay = (dtstart && dtstart.isDate); + if (!oldItem || !equalDatetimes(dtstart, oldItem.entryDate) + || !equalDatetimes(dtend, oldItem.dueDate)) { + params += ("&dtstart=" + getIcalUTC(dtstart)); + if (dtstart) { + params += ("&X-NSCP-DTSTART-TZID=" + + "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + + encodeURIComponent(this.getAlignedTimezone(dtstart.timezone))); + } + params += ("&due=" + getIcalUTC(dtend)); + if (dtend) { + params += ("&X-NSCP-DUE-TZID=" + + "X-NSCP-ORIGINAL-OPERATION=X-NSCP-WCAP-PROPERTY-REPLACE^" + + encodeURIComponent(this.getAlignedTimezone(dtend.timezone))); + } + params += (bIsAllDay ? "&isAllDay=1" : "&isAllDay=0"); + } + log("dtstart=" + dtstart + "\ndtend=" + dtend + "\nid=" + item.id, this); + + if (!oldItem || item.percentComplete != oldItem.percentComplete) + params += ("&percent=" + item.percentComplete.toString(10)); + if (!oldItem || !equalDatetimes(item.completedDate, oldItem.completedDate)) + params += ("&completed=" + getIcalUTC(item.completedDate)); + } + + val = diffProperty(item, oldItem, "TRANSP"); + if (val) { + switch (val) { + case "TRANSPARENT": + params += "&transparent=1"; + break; + case "OPAQUE": + params += "&transparent=0"; + break; + default: + params += ("&transparent=" + + (((icsClass == "PRIVATE") || bIsAllDay) ? "1" : "0")); + break; + } + } + } // PUBLISH, REQUEST + + if (params.length == 0) { + log("no change at all.", this); + if (LOG_LEVEL > 2) { + log("old item:\n" + oldItem.icalString + "\n\nnew item:\n" + + item.icalString, this); + } + request.execRespFunc(null, item); + } + else { + if (item.id) + params += ("&uid=" + encodeURIComponent(item.id)); + + // be picky about create/modify: + // WCAP_STORE_TYPE_CREATE, WCAP_STORE_TYPE_MODIFY + params += (bAddItem ? "&storetype=1" : "&storetype=2"); + + if (bIsParent) // THIS AND ALL INSTANCES: + params += "&mod=4"; + else { + // THIS INSTANCE: + var rid = item.recurrenceId; + if (rid.isDate) { + // cs does not accept DATE: + rid = rid.clone(); + rid.isDate = false; + } + params += ("&mod=1&rid=" + getIcalUTC(rid)); + } + + if (bOrgRequest) + params += "&method=2"; // REQUEST + else if (bAttendeeReply) + params += "&method=4"; // REPLY + // else PUBLISH (default) + + params += "&replace=1"; // (update) don't append to any lists + params += "&fetch=1&relativealarm=1&compressed=1&recurring=1"; + params += "&emailorcalid=1&fmt-out=text%2Fcalendar"; + + this.issueNetworkRequest( + request, netRespFunc, + stringToIcal, bIsEvent ? "storeevents" : "storetodos", params, + calIWcapCalendar.AC_COMP_READ | + calIWcapCalendar.AC_COMP_WRITE); } - url += ("&contacts=" + encodeURIComponent(contacts)); - - if (bIsParent) - url += this.encodeRecurrenceParams( item, oldItem ); - - this.session.issueAsyncRequest( - url, stringToIcal, receiverFunc ); - return true; }; -calWcapCalendar.prototype.tunnelXProps = function( destItem, srcItem ) +calWcapCalendar.prototype.tunnelXProps = +function calWcapCalendar_tunnelXProps(destItem, srcItem) { // xxx todo: temp workaround for bug in calItemBase.js if (!isParent(srcItem)) return; - var en = srcItem.propertyEnumerator; - while (en.hasMoreElements()) { - var prop = en.getNext().QueryInterface( + var enumerator = srcItem.propertyEnumerator; + while (enumerator.hasMoreElements()) { + var prop = enumerator.getNext().QueryInterface( Components.interfaces.nsIProperty); var name = prop.name; if (name.indexOf("X-MOZ-") == 0) { if (LOG_LEVEL > 1) - this.log( "tunneling " + name ); + log("tunneling " + name, this); destItem.setProperty(name, prop.value); } } }; -calWcapCalendar.prototype.adoptItem_resp = function( - wcapResponse, newItem_, listener ) +calWcapCalendar.prototype.adoptItem = +function calWcapCalendar_adoptItem(item, listener) { - var item = null; - try { - var icalRootComp = wcapResponse.data; // first statement, may throw - - var items = this.parseItems( - icalRootComp, - Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS, - 0, null, null, true /* bLeaveMutable */ ); - if (items.length < 1) - throw new Components.Exception("empty VCALENDAR returned!"); - if (items.length > 1) - this.notifyError( "unexpected number of items: " + items.length ); - item = items[0]; - this.tunnelXProps(item, newItem_); - item.makeImmutable(); - - this.log( "item.id=" + item.id ); - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.ADD, - item.id, item ); - } - this.notifyObservers( "onAddItem", [item] ); - // xxx todo: maybe log request status - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.ADD, - item == null ? null : item.id, exc ); - } - this.notifyError( exc ); - } -}; - -calWcapCalendar.prototype.adoptItem_queued = function( item, listener ) -{ - this.log( "adoptItem() call: " + item.title ); - try { - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_WRITE); - - // xxx todo: workaround really necessary for adding an occurrence? - var oldItem = null; - if (!isParent(item)) { - this.logError( "adoptItem(): unexpected proxy!" ); - debugger; - item.parentItem.recurrenceInfo.modifyException( item ); - oldItem = item; // patch to modify - } - - var this_ = this; - if (!this.storeItem( - item, oldItem, - function( wcapResponse ) { - this_.adoptItem_resp( wcapResponse, item, listener ); - } )) - { + var this_ = this; + var request = new calWcapRequest( + function adoptItem_resp(request, err, newItem) { if (listener) { listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.ADD, - item.id, item ); + this_.superCalendar, getResultCode(err), + calIOperationListener.ADD, + err ? item.id : newItem.id, + err ? err : newItem); } - this.notifyObservers( "onAddItem", [item] ); + if (err) + this_.notifyError(err); + else + this_.notifyObservers("onAddItem", [newItem]); + }, + log("adoptItem() call: " + item.title, this)); + + try { + if (!isParent(item)) { + this_.logError("adoptItem(): unexpected proxy!"); + debugger; + item.parentItem.recurrenceInfo.modifyException(item); } + this.storeItem(true/*bAddItem*/, + item, null, request, + function netResp(err, icalRootComp) { + if (err) + throw err; + var items = this_.parseItems( + icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, + 0, null, null, true /* bLeaveMutable */); + if (items.length < 1) + throw new Components.Exception("empty VCALENDAR returned!"); + if (items.length > 1) { + this_.notifyError("unexpected number of items: " + + items.length); + } + var newItem = items[0]; + this_.tunnelXProps(newItem, item); + item.makeImmutable(); + // invalidate cached results: + delete this_.m_cachedResults; + log("newItem.id=" + newItem.id, this_); + // xxx todo: may log request status + request.execRespFunc(null, newItem); + }); } catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.ADD, - item.id, exc ); - } - this.notifyError( exc ); + request.execRespFunc(exc); } - this.log( "adoptItem() returning." ); + return request; +} + +calWcapCalendar.prototype.addItem = +function calWcapCalendar_addItem(item, listener) +{ + this.adoptItem(item.clone(), listener); }; -calWcapCalendar.prototype.addItem = function( item, listener ) +calWcapCalendar.prototype.modifyItem = +function calWcapCalendar_modifyItem(newItem, oldItem, listener) { - this.adoptItem( item.clone(), listener ); -}; - -calWcapCalendar.prototype.modifyItem_resp = function( - wcapResponse, newItem_, oldItem, listener ) -{ - var item = null; + var this_ = this; + var request = new calWcapRequest( + function modifyItem_resp(request, err, item) { + if (listener) { + listener.onOperationComplete( + this_.superCalendar, getResultCode(err), + calIOperationListener.MODIFY, + newItem.id, err ? err : item); + } + if (err) + this_.notifyError(err); + else + this_.notifyObservers("onModifyItem", [item, oldItem]); + }, + log("modifyItem() call: " + newItem.id, this)); + try { - var icalRootComp = wcapResponse.data; // first statement, may throw - - var items = this.parseItems( - icalRootComp, - Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS, - 0, null, null, true /* bLeaveMutable */ ); - if (items.length < 1) - throw new Components.Exception("empty VCALENDAR returned!"); - if (items.length > 1) - this.notifyError( "unexpected number of items: " + items.length ); - item = items[0]; - this.tunnelXProps(item, newItem_); - item.makeImmutable(); - - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.MODIFY, - item.id, item ); - } - this.notifyObservers( "onModifyItem", [item, oldItem] ); - // xxx todo: maybe log request status - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.MODIFY, - item == null ? null : item.id, exc ); - } - this.notifyError( exc ); - } -}; - -calWcapCalendar.prototype.modifyItem_queued = function( - newItem, oldItem, listener ) -{ - this.log( "modifyItem() call: " + newItem.id ); - try { - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_WRITE); - if (!newItem.id) throw new Components.Exception("new item has no id!"); - - var this_ = this; - if (!this.storeItem( - newItem, oldItem, - function( wcapResponse ) { - this_.modifyItem_resp( - wcapResponse, newItem, oldItem, listener); - } )) - { - // nothing has changed, just notify item: + this.storeItem(false/*bAddItem*/, + newItem, + // pass null for oldItem when creating new exceptions: + (oldItem && !isParent(newItem) && isParent(oldItem)) ? null : oldItem, + request, + function netResp(err, icalRootComp) { + if (err) + throw err; + var items = this_.parseItems( + icalRootComp, + calICalendar.ITEM_FILTER_ALL_ITEMS, + 0, null, null, true /* bLeaveMutable */); + if (items.length < 1) + throw new Components.Exception("empty VCALENDAR returned!"); + if (items.length > 1) { + this_.notifyError("unexpected number of items: " + + items.length); + } + var item = items[0]; + this_.tunnelXProps(item, newItem); + item.makeImmutable(); + // invalidate cached results: + delete this_.m_cachedResults; + // xxx todo: maybe log request status + request.execRespFunc(null, item); + }); + } + catch (exc) { + request.execRespFunc(exc); + } + return request; +}; + +calWcapCalendar.prototype.deleteItem = +function calWcapCalendar_deleteItem(item, listener) +{ + var this_ = this; + var request = new calWcapRequest( + function deleteItem_resp(request, err) { + // xxx todo: need to notify about each deleted item if multiple? if (listener) { listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.MODIFY, - oldItem.id, oldItem ); + this_.superCalendar, getResultCode(err), + calIOperationListener.DELETE, + item.id, err ? err : item); } - this.notifyObservers( "onModifyItem", [oldItem, oldItem] ); - } - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.MODIFY, - newItem.id, exc ); - } - this.notifyError( exc ); - } - this.log( "modifyItem() returning." ); -}; - -calWcapCalendar.prototype.deleteItem_resp = function( - wcapResponse, item, listener ) -{ + if (err) + this_.notifyError(err); + else + this_.notifyObservers("onDeleteItem", [item]); + }, + log("deleteItem() call: " + item.id, this)); + try { - var xml = wcapResponse.data; // first statement, may throw - - // xxx todo: need to notify about each deleted item if multiple? - if (item.isMutable) { - item.makeImmutable(); - } - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.DELETE, - item.id, item ); - } - this.notifyObservers( "onDeleteItem", [item] ); - if (LOG_LEVEL > 0) { - this.log( "deleteItem_resp(): " + - getWcapRequestStatusString(xml) ); - } - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.DELETE, - item.id, exc ); - } - this.notifyError( exc ); - } -}; - -calWcapCalendar.prototype.deleteItem_queued = function( item, listener ) -{ - this.log( "deleteItem() call: " + item.id ); - try { - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_WRITE); - - if (item.id == null) + if (!item.id) throw new Components.Exception("no item id!"); - - var url = this.getCommandUrl( - isEvent(item) ? "deleteevents_by_id" : "deletetodos_by_id" ); - url += ("&uid=" + encodeURIComponent(item.id)); - + var params = ("&uid=" + encodeURIComponent(item.id)); if (isParent(item)) // delete THIS AND ALL: - url += "&mod=4&rid=0"; + params += "&mod=4&rid=0"; else // delete THIS INSTANCE: - url += ("&mod=1&rid=" + getIcalUTC(item.recurrenceId)); + params += ("&mod=1&rid=" + getIcalUTC(item.recurrenceId)); + params += "&fmt-out=text%2Fxml"; - var this_ = this; - this.session.issueAsyncRequest( - url + "&fmt-out=text%2Fxml", stringToXml, - function( wcapResponse ) { - this_.deleteItem_resp( wcapResponse, item, listener ); - } ); + this.issueNetworkRequest( + request, + function netResp(err, xml) { + if (err) + throw err; + // invalidate cached results: + delete this_.m_cachedResults; + if (LOG_LEVEL > 0) + log("deleteItem(): " + getWcapRequestStatusString(xml), this_); + }, + stringToXml, isEvent(item) ? "deleteevents_by_id" : "deletetodos_by_id", + params, calIWcapCalendar.AC_COMP_WRITE); } catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.DELETE, - item.id, exc ); - } - this.notifyError( exc ); + request.execRespFunc(exc); } - this.log( "deleteItem() returning." ); + return request; }; -calWcapCalendar.prototype.parseItems = function( - icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd, - bLeaveMutable ) +calWcapCalendar.prototype.parseItems = function calWcapCalendar_parseItems( + icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd, bLeaveMutable) { var items = []; - this.parseItems_( - function(srcItems) { items = items.concat(srcItems) }, - icalRootComp, items, maxResult, rangeStart, rangeEnd, - bLeaveMutable); - return items; -}; - -calWcapCalendar.prototype.parseItems_ = function( - receiverFunc, - icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd, - bLeaveMutable ) -{ - var nItems = 0; var unexpandedItems = []; var uid2parent = {}; var excItems = []; var componentType = "ANY"; - switch (itemFilter &Components.interfaces.calICalendar.ITEM_FILTER_TYPE_ALL) - { - case Components.interfaces.calICalendar.ITEM_FILTER_TYPE_TODO: + switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) { + case calICalendar.ITEM_FILTER_TYPE_TODO: componentType = "VTODO"; break; - case Components.interfaces.calICalendar.ITEM_FILTER_TYPE_EVENT: + case calICalendar.ITEM_FILTER_TYPE_EVENT: componentType = "VEVENT"; break; } @@ -803,48 +705,44 @@ calWcapCalendar.prototype.parseItems_ = function( icalRootComp, componentType, function( subComp ) { - function patchTimezone( subComp, attr, xprop ) { + function patchTimezone(subComp, attr, xprop) { var dt = subComp[attr]; - if (dt != null) { + if (dt) { if (LOG_LEVEL > 2) { - this_.log( attr + " is " + dt ); + log(attr + " is " + dt, this_); } - var tzid = subComp.getFirstProperty( xprop ); + var tzid = subComp.getFirstProperty(xprop); if (tzid != null) { subComp[attr] = dt.getInTimezone(tzid.value); if (LOG_LEVEL > 2) { - this_.log( "patching " + xprop + " from " + - dt + " to " + subComp[attr] ); + log("patching " + xprop + " from " + + dt + " to " + subComp[attr], this_); } } } } - patchTimezone( subComp, "startTime", "X-NSCP-DTSTART-TZID" ); + patchTimezone(subComp, "startTime", "X-NSCP-DTSTART-TZID"); var item = null; switch (subComp.componentType) { case "VEVENT": { - patchTimezone( subComp, "endTime", "X-NSCP-DTEND-TZID" ); + patchTimezone(subComp, "endTime", "X-NSCP-DTEND-TZID"); item = new CalEvent(); item.icalComponent = subComp; break; } case "VTODO": { - patchTimezone( subComp, "dueTime", "X-NSCP-DUE-TZID" ); + patchTimezone(subComp, "dueTime", "X-NSCP-DUE-TZID"); item = new CalTodo(); item.icalComponent = subComp; - switch (itemFilter & Components.interfaces.calICalendar - .ITEM_FILTER_COMPLETED_ALL) - { - case Components.interfaces.calICalendar - .ITEM_FILTER_COMPLETED_YES: + switch (itemFilter & calICalendar.ITEM_FILTER_COMPLETED_ALL) { + case calICalendar.ITEM_FILTER_COMPLETED_YES: if (!item.isCompleted) { delete item; item = null; } break; - case Components.interfaces.calICalendar - .ITEM_FILTER_COMPLETED_NO: + case calICalendar.ITEM_FILTER_COMPLETED_NO: if (item.isCompleted) { delete item; item = null; @@ -854,9 +752,9 @@ calWcapCalendar.prototype.parseItems_ = function( // if (item && // item.alarmOffset && !item.entryDate && !item.dueDate) { // // xxx todo: loss on roundtrip -// this_.log( "app currently does not support " + +// log( "app currently does not support " + // "absolute alarm trigger datetimes. " + -// "Removing alarm from item: " + item.title ); +// "Removing alarm from item: " + item.title, this_); if (item) { // xxx todo: todo alarms currently off item.alarmOffset = null; item.alarmLastAck = null; @@ -864,18 +762,18 @@ calWcapCalendar.prototype.parseItems_ = function( break; } } - if (item != null) { - var contactsProp = subComp.getFirstProperty("CONTACT"); - if (contactsProp) { // stamp[:lastack] - var ar = contactsProp.value.split(":"); - if (ar.length > 1) { - var lastAck = ar[1]; - if (lastAck.length > 0) { // shift to alarm comp: - item.alarmLastAck = getDatetimeFromIcalString( - lastAck); // TZID is UTC - } - } - } + if (item) { +// var contactsProp = subComp.getFirstProperty("CONTACT"); +// if (contactsProp) { // stamp[:lastack] +// var ar = contactsProp.value.split(":"); +// if (ar.length > 1) { +// var lastAck = ar[1]; +// if (lastAck.length > 0) { // shift to alarm comp: +// item.alarmLastAck = getDatetimeFromIcalString( +// lastAck); // TZID is UTC +// } +// } +// } if (!item.title) { // assumed to look at a subscribed calendar, @@ -896,7 +794,7 @@ calWcapCalendar.prototype.parseItems_ = function( item.recurrenceInfo = null; var startDate = (isEvent(item) ? item.startDate : item.entryDate); - if (startDate.isDate && !rid.isDate) { + if (startDate && startDate.isDate && !rid.isDate) { // cs ought to return proper all-day RECURRENCE-ID! // get into startDate's timezone before cutting: rid = rid.getInTimezone(startDate.timezone); @@ -904,28 +802,28 @@ calWcapCalendar.prototype.parseItems_ = function( item.recurrenceId = rid; } if (LOG_LEVEL > 1) { - this_.log( "exception item: " + item.title + - "\nrid=" + rid.icalString, - "item.id=" + item.id ); + log("exception item: " + item.title + + "\nrid=" + rid.icalString + + "\nitem.id=" + item.id, this_); } - excItems.push( item ); + excItems.push(item); } else if (item.recurrenceInfo) { - unexpandedItems.push( item ); + unexpandedItems.push(item); uid2parent[item.id] = item; } - else if (maxResult == 0 || nItems < maxResult) { + else if (maxResult == 0 || items.length < maxResult) { if (LOG_LEVEL > 2) { - this_.log( "item: " + item.title + "\n" + - item.icalString ); + log("item: " + item.title + "\n" + item.icalString, + this_); } if (!bLeaveMutable) item.makeImmutable(); - receiverFunc( [item] ); + items.push(item); } } }, - maxResult ); + maxResult); // tag "exceptions", i.e. items with rid: for each ( var item in excItems ) { @@ -936,51 +834,46 @@ calWcapCalendar.prototype.parseItems_ = function( parent.recurrenceInfo.modifyException( item ); } else { - this.logError( "parseItems(): no parent item for " + item.title + - ", rid=" + item.recurrenceId.icalString, - "item.id=" + item.id ); - // xxx todo: due to a server bug, in some scenarions the returned - // data is lacking the parent item, leave parentItem open - if ((itemFilter & Components.interfaces.calICalendar - .ITEM_FILTER_CLASS_OCCURRENCES) == 0) { + logError("parseItems(): no parent item for " + item.title + + ", rid=" + item.recurrenceId.icalString + + ", item.id=" + item.id, this); + // due to a server bug, in some scenarions the returned + // data is lacking the parent item, leave parentItem open then + if ((itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) == 0) item.recurrenceId = null; - } if (!bLeaveMutable) item.makeImmutable(); - receiverFunc( [item] ); + items.push(item); } } - if (itemFilter & Components.interfaces.calICalendar - .ITEM_FILTER_CLASS_OCCURRENCES) - { + if (itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) { for each ( var item in unexpandedItems ) { - if (maxResult != 0 && nItems >= maxResult) + if (maxResult != 0 && items.length >= maxResult) break; if (!bLeaveMutable) item.makeImmutable(); var occurrences = item.recurrenceInfo.getOccurrences( rangeStart, rangeEnd, - maxResult == 0 ? 0 : maxResult - nItems, + maxResult == 0 ? 0 : maxResult - items.length, {} ); if (LOG_LEVEL > 1) { - this.log( "item: " + item.title + " has " + - occurrences.length.toString() + " occurrences." ); + log("item: " + item.title + " has " + + occurrences.length.toString() + " occurrences.", this); if (LOG_LEVEL > 2) { for each ( var occ in occurrences ) { - this.log("item: " + occ.title + "\n" + occ.icalString); + log("item: " + occ.title + "\n" + occ.icalString, this); } } } // only proxies returned: - receiverFunc( occurrences ); - nItems += occurrences.length; + items = items.concat(occurrences); } } else { if (maxResult != 0 && - (nItems + unexpandedItems.length) > maxResult) { - unexpandedItems.length = (maxResult - nItems); + (items.length + unexpandedItems.length) > maxResult) { + unexpandedItems.length = (maxResult - items.length); } if (!bLeaveMutable) { for each ( var item in unexpandedItems ) { @@ -989,214 +882,101 @@ calWcapCalendar.prototype.parseItems_ = function( } if (LOG_LEVEL > 2) { for each ( var item in unexpandedItems ) { - this.log( "item: " + item.title + "\n" + item.icalString ); + log("item: " + item.title + "\n" + item.icalString, this); } } - receiverFunc( unexpandedItems ); - nItems += unexpandedItems.length; + items = items.concat(unexpandedItems); } - if (LOG_LEVEL > 1) { - this.log( "parseItems_(): notified " + nItems + " items" ); - } + if (LOG_LEVEL > 1) + log("parseItems(): returning " + items.length + " items", this); + return items; }; -calWcapCalendar.prototype.getItem_queued = function( id, listener ) -{ - // xxx todo: test - // xxx todo: howto detect whether to call - // fetchevents_by_id ot fetchtodos_by_id? - // currently drag/drop is implemented for events only, - // try events first, fallback to todos... in the future... - this.log( ">>>>>>>>>>>>>>>> getItem() call!"); - try { - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_READ); +// calWcapCalendar.prototype.getItem = function( id, listener ) +// { +// // xxx todo: test +// // xxx todo: howto detect whether to call +// // fetchevents_by_id ot fetchtodos_by_id? +// // currently drag/drop is implemented for events only, +// // try events first, fallback to todos... in the future... +// this.log( ">>>>>>>>>>>>>>>> getItem() call!"); +// try { +// this.assureAccess(calIWcapCalendar.AC_COMP_READ); - var this_ = this; - var syncResponseFunc = function( wcapResponse ) { - var icalRootComp = wcapResponse.data; // first statement, may throw - var items = this_.parseItems( - icalRootComp, - Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS, - 1, null, null ); - if (items.length < 1) - throw new Components.Exception("no such item!"); - if (items.length > 1) { - this_.notifyError( - "unexpected number of items: " + items.length ); - } - item = items[0]; - if (listener != null) { - listener.onGetResult( - this_.superCalendar, Components.results.NS_OK, - Components.interfaces.calIItemBase, - this_.log( "getItem(): success." ), - items.length, items ); - listener.onOperationComplete( - this_.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.GET, - items.length == 1 ? items[0].id : null, null ); - this_.log( "item delivered." ); - } - }; +// var this_ = this; +// var syncResponseFunc = function( wcapResponse ) { +// var icalRootComp = wcapResponse.data; // first statement, may throw +// var items = this_.parseItems( +// icalRootComp, +// calICalendar.ITEM_FILTER_ALL_ITEMS, +// 1, null, null ); +// if (items.length < 1) +// throw new Components.Exception("no such item!"); +// if (items.length > 1) { +// this_.notifyError( +// "unexpected number of items: " + items.length ); +// } +// item = items[0]; +// if (listener) { +// listener.onGetResult( +// this_.superCalendar, Components.results.NS_OK, +// Components.interfaces.calIItemBase, +// log("getItem(): success.", this_), +// items.length, items ); +// listener.onOperationComplete( +// this_.superCalendar, Components.results.NS_OK, +// calIOperationListener.GET, +// items.length == 1 ? items[0].id : null, null ); +// this_.log( "item delivered." ); +// } +// }; - var params = ("&relativealarm=1&compressed=1&recurring=1" + - "&fmt-out=text%2Fcalendar"); - params += ("&uid=" + encodeURIComponent(id)); - try { - // most common: event - this.session.issueSyncRequest( - this.getCommandUrl( "fetchevents_by_id" ) + params, - stringToIcal, syncResponseFunc ); - } - catch (exc) { - // try again, may be a task: - this.session.issueSyncRequest( - this.getCommandUrl( "fetchtodos_by_id" ) + params, - stringToIcal, syncResponseFunc ); - } - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.GET, - null, exc ); - } - if (testResultCode(exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED)) { - // silently ignore login failed, no calIObserver UI: - this.logError( "getItem() ignored: " + errorToString(exc) ); - } - else - this.notifyError( exc ); - } - this.log( "getItem() returning." ); -}; +// var params = ("&relativealarm=1&compressed=1&recurring=1" + +// "&emailorcalid=1&fmt-out=text%2Fcalendar"); +// params += ("&uid=" + encodeURIComponent(id)); +// try { +// // xxx todo!!!! -calWcapCalendar.prototype.getItems_resp = function( - wcapResponse, - itemFilter, maxResult, rangeStart, rangeEnd, listener ) -{ - try { - var exc = wcapResponse.exception; - // check whether access is denied, - // then try to use free-busy information instead: - // xxx todo: reuse these bits here; should be shifted to - // getItems_queued directly if (!checkAccess(AC_COMP_READ))... - if (testResultCode( exc, Components.interfaces. - calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR)) { - if (listener && - // xxx todo: ignore errors on Todo retrieval, callers ought - // to check whether Read-Access is granted before - // calling getItems() in the future. - (itemFilter & - Components.interfaces.calICalendar.ITEM_FILTER_TYPE_EVENT) && - rangeStart && rangeEnd) - { - var this_ = this; - var freeBusyListener = { // calIWcapFreeBusyListener: - onGetFreeBusyTimes: - function( rc, requestId, calId, count, entries ) - { - if (rc == Components.results.NS_OK) { - var items = []; - for each ( var entry in entries ) { - var item = new CalEvent(); - item.id = (g_busyPhantomItemUuidPrefix + - entry.dtRangeStart.icalString); - item.calendar = this_.superCalendar; - item.title = g_busyItemTitle; - item.startDate = entry.dtRangeStart; - item.endDate = entry.dtRangeEnd; - item.makeImmutable(); - items.push(item); - } - listener.onGetResult( - this_.superCalendar, Components.results.NS_OK, - Components.interfaces.calIItemBase, - this_.log( "getItems_resp() using free-busy " + - "information: success." ), - items.length, items ); - listener.onOperationComplete( - this_.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.GET, - items.length == 1 ? items[0].id : null, null ); - this_.log( items.length.toString() + - " freebusy items delivered." ); - } - else { - // if even availability is denied: - listener.onOperationComplete( - this_.superCalendar, - Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.GET, - null, rc ); - } - } - }; - // for the exotic case that someone can read this calendar, - // but has no freebusy access... - // cannot check this in session, because that API is also for - // looking up users... - this.assureAccess( - Components.interfaces.calIWcapCalendar.AC_FREEBUSY); - this.session.getFreeBusyTimes( - this.calId, rangeStart, rangeEnd, true /*bBusyOnly*/, - freeBusyListener, true/*async*/, 0 /*requestId*/ ); - } - return; - } - - var icalRootComp = wcapResponse.data; // first statement, may throw - - if (listener) { - var this_ = this; - function deliverItems(items) { - listener.onGetResult( - this_.superCalendar, Components.results.NS_OK, - Components.interfaces.calIItemBase, - "WCAP getItems_resp()", - items.length, items ); - } - this.parseItems_( - deliverItems, - icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd ); - listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIOperationListener.GET, - null, null ); - } - else { - // just to check returned data: - this.parseItems( - icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd ); - } - this.log( "getItems(): success." ); - } - catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.GET, - null, exc ); - } - this.notifyError( exc ); - } -}; +// // most common: event +// this.session.issueSyncRequest( +// this.getCommandUrl( "fetchevents_by_id" ) + params, +// stringToIcal, syncResponseFunc ); +// } +// catch (exc) { +// // try again, may be a task: +// this.session.issueSyncRequest( +// this.getCommandUrl( "fetchtodos_by_id" ) + params, +// stringToIcal, syncResponseFunc ); +// } +// } +// catch (exc) { +// if (listener != null) { +// listener.onOperationComplete( +// this.superCalendar, Components.results.NS_ERROR_FAILURE, +// calIOperationListener.GET, +// null, exc ); +// } +// if (getResultCode(exc) == calIWcapErrors.WCAP_LOGIN_FAILED) { +// // silently ignore login failed, no calIObserver UI: +// this.logError( "getItem() ignored: " + errorToString(exc) ); +// } +// else +// this.notifyError( exc ); +// } +// this.log( "getItem() returning." ); +// }; -function getItemFilterUrlPortions( itemFilter ) +function getItemFilterParams(itemFilter) { - var url = ""; - switch (itemFilter & - Components.interfaces.calICalendar.ITEM_FILTER_TYPE_ALL) { - case Components.interfaces.calICalendar.ITEM_FILTER_TYPE_TODO: - url += "&component-type=todo"; break; - case Components.interfaces.calICalendar.ITEM_FILTER_TYPE_EVENT: - url += "&component-type=event"; break; + var params = ""; + switch (itemFilter & calICalendar.ITEM_FILTER_TYPE_ALL) { + case calICalendar.ITEM_FILTER_TYPE_TODO: + params += "&component-type=todo"; break; + case calICalendar.ITEM_FILTER_TYPE_EVENT: + params += "&component-type=event"; break; } - const calIWcapCalendar = Components.interfaces.calIWcapCalendar; var compstate = ""; // if (itemFilter & calIWcapCalendar.ITEM_FILTER_REPLY_DECLINED) // compstate += ";REPLY-DECLINED"; @@ -1213,338 +993,418 @@ function getItemFilterUrlPortions( itemFilter ) // if (itemFilter & calIWcapCalendar.ITEM_FILTER_REQUEST_WAITFORREPLY) // compstate += ";REQUEST-WAITFORREPLY"; if (compstate.length > 0) - url += ("&compstate=" + compstate.substr(1)); - return url; + params += ("&compstate=" + compstate.substr(1)); + return params; } -calWcapCalendar.prototype.getItems_queued = function( - itemFilter, maxResult, rangeStart, rangeEnd, listener ) +calWcapCalendar.prototype.getItems = +function calWcapCalendar_getItems(itemFilter, maxResult, rangeStart, rangeEnd, listener) { - // assure DATETIMEs: - if (rangeStart != null && rangeStart.isDate) { + // assure DATE-TIMEs: + if (rangeStart && rangeStart.isDate) { rangeStart = rangeStart.clone(); rangeStart.isDate = false; } - if (rangeEnd != null && rangeEnd.isDate) { + if (rangeEnd && rangeEnd.isDate) { rangeEnd = rangeEnd.clone(); rangeEnd.isDate = false; } var zRangeStart = getIcalUTC(rangeStart); var zRangeEnd = getIcalUTC(rangeEnd); - this.log( "getItems():\n\titemFilter=0x" + itemFilter.toString(16) + - ",\n\tmaxResult=" + maxResult + - ",\n\trangeStart=" + zRangeStart + - ",\n\trangeEnd=" + zRangeEnd ); + + var this_ = this; + var request = new calWcapRequest( + function getItems_resp(request, err, data) { + var rc = getResultCode(err); + if (err) { + if (listener) { + listener.onOperationComplete( + this_.superCalendar, rc, + calIOperationListener.GET, + null, err); + } + if (getResultCode(err) != calIWcapErrors.WCAP_LOGIN_FAILED) { + this_.notifyError(err); + } + } + else { + log("getItems(): success.", this_); + if (listener) { + listener.onOperationComplete( + this_.superCalendar, rc, + calIOperationListener.GET, + null, null); + } + } + }, + log("getItems():\n\titemFilter=0x" + itemFilter.toString(0x10) + + ",\n\tmaxResult=" + maxResult + + ",\n\trangeStart=" + zRangeStart + + ",\n\trangeEnd=" + zRangeEnd, this)); + + if (this.session.aboutToLogout) { // limiting the amount of network traffic: + log("about to logout, no results.", this); + request.execRespFunc(null, []); + return request; + } + + // m_cachedResults holds the last data revtrieval. This is expecially useful when + // switching on multiple subcriptions: the composite calendar multiplexes getItems() + // calls to all composited calendars over and over again, most often on the same + // date range (as the user usually looks at the same view). + // This will most likely vanish when a better caching is implemented in the views, + // or WCAP local storage caching has sufficient performance. + // The cached results will be invalidated after 2 minutes to reflect incoming invitations. + if (CACHE_LAST_RESULTS > 0 && this.m_cachedResults) { + for each (var entry in this.m_cachedResults) { + if ((itemFilter == entry.itemFilter) && + equalDatetimes(rangeStart, entry.rangeStart) && + equalDatetimes(rangeEnd, entry.rangeEnd)) { + log("reusing last getItems() cached data.", this); + if (listener) { + listener.onGetResult( + this.superCalendar, + Components.results.NS_OK, + Components.interfaces.calIItemBase, + "getItems()", entry.results.length, entry.results); + } + request.execRespFunc(null, entry.results); + return request; + } + } + } + try { - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_READ); - - var url = this.getCommandUrl( "fetchcomponents_by_range" ); - url += ("&relativealarm=1&compressed=1&recurring=1" + - "&fmt-out=text%2Fcalendar"); - + var params = ("&relativealarm=1&compressed=1&recurring=1" + + "&emailorcalid=1&fmt-out=text%2Fcalendar"); // setting component-type, compstate filters: - url += getItemFilterUrlPortions(itemFilter); - + params += getItemFilterParams(itemFilter); if (maxResult > 0) - url += ("&maxResult=" + maxResult); - url += ("&dtstart=" + zRangeStart); - url += ("&dtend=" + zRangeEnd); + params += ("&maxResult=" + maxResult); + params += ("&dtstart=" + zRangeStart); + params += ("&dtend=" + zRangeEnd); - var this_ = this; - this.session.issueAsyncRequest( - url, stringToIcal, - function( wcapResponse ) { - this_.getItems_resp( wcapResponse, - itemFilter, maxResult, - rangeStart, rangeEnd, listener ); - } ); + this.issueNetworkRequest( + request, + function netResp(err, icalRootComp) { + if (err) { + if (getResultCode(err) == + calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR) + { + // try free-busy times: + if (listener && + (itemFilter & calICalendar.ITEM_FILTER_TYPE_EVENT) && + rangeStart && rangeEnd) + { + var freeBusyListener = { // calIWcapRequestResultListener: + onRequestResult: + function freeBusyListener_onRequestResult(request, result) { + if (!request.succeeded) + throw request.status; + var items = []; + for each ( var period in result ) { + var item = new CalEvent(); + item.id = (g_busyPhantomItemUuidPrefix + + period.start.icalString); + item.calendar = this_.superCalendar; + item.title = g_busyItemTitle; + item.startDate = period.start; + item.endDate = period.end; + item.makeImmutable(); + items.push(item); + } + listener.onGetResult( + this_.superCalendar, + Components.results.NS_OK, + Components.interfaces.calIItemBase, + "getItems()/free-busy", items.length, items); + } + }; + request.attachSubRequest( + this_.session.getFreeBusyTimes( + this_.calId, rangeStart, rangeEnd, true /*bBusy*/, + freeBusyListener)); + } + } + else + throw err; + } + else if (listener) { + var items = this_.parseItems( + icalRootComp, itemFilter, maxResult, + rangeStart, rangeEnd); + + if (CACHE_LAST_RESULTS > 0) { + // auto invalidate after X minutes: + if (!this_.m_cachedResultsTimer) { + var callback = { + notify: function notify(timer) { + if (!this_.m_cachedResults) + return; + var now = (new Date()).getTime(); + // sort out old entries: + entries = []; + for (var i = 0; i < this_.m_cachedResults.length; ++i) { + var entry = this_.m_cachedResults[i]; + if ((now - entry.stamp) < + (CACHE_LAST_RESULTS_INVALIDATE * 1000)) { + entries.push(entry); + } + else { + log("invalidating cached entry:\n\trangeStart=" + + getIcalUTC(entry.rangeStart) + "\n\trangeEnd=" + + getIcalUTC(entry.rangeEnd), this_); + } + } + this_.m_cachedResults = entries; + } + }; + // sort out freq: + var freq = Math.min( + 20, // default: 20secs + Math.max(1, CACHE_LAST_RESULTS_INVALIDATE)); + log("cached results sort out timer freq: " + freq, this_); + this_.m_cachedResultsTimer = new Timer(); + this_.m_cachedResultsTimer.initWithCallback( + callback, freq * 1000, + Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + } + if (!this_.m_cachedResults) + this_.m_cachedResults = []; + var entry = { + stamp: (new Date()).getTime(), + itemFilter: itemFilter, + rangeStart: (rangeStart ? rangeStart.clone() : null), + rangeEnd: (rangeEnd ? rangeEnd.clone() : null), + results: items + }; + this_.m_cachedResults.unshift(entry); + if (this_.m_cachedResults.length > CACHE_LAST_RESULTS) + this_.m_cachedResults.length = CACHE_LAST_RESULTS; + } + + listener.onGetResult( + this_.superCalendar, + Components.results.NS_OK, + Components.interfaces.calIItemBase, + "getItems()", items.length, items); + } + }, + stringToIcal, "fetchcomponents_by_range", params, + calIWcapCalendar.AC_COMP_READ); } catch (exc) { - if (listener != null) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_ERROR_FAILURE, - Components.interfaces.calIOperationListener.GET, - null, exc ); - } - if (testResultCode(exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED)) { - // silently ignore login failed, no calIObserver UI: - this.logError( "getItems() ignored: " + errorToString(exc) ); - } - else - this.notifyError( exc ); + request.execRespFunc(exc); } - this.log( "getItems() returning." ); + return request; }; +// function calWcapSyncOperationListener() { +// this.superClass(respFunc); +// this.wrappedJSObject = this; +// } +// subClass(calWcapSyncOperationListener, calWcapRequest); -function SyncState( finishFunc, abortFunc ) { - this.m_finishFunc = finishFunc; - this.m_abortFunc = abortFunc; -} -SyncState.prototype = { - m_state: 0, - m_finishFunc: null, - m_abortFunc: null, - m_exc: null, - - acquire: function() { /*this.checkAborted();*/ ++this.m_state; }, - release: function() { - /*this.checkAborted();*/ - --this.m_state; -// logMessage( "sync-state", "m_state = " + this.m_state ); - if (this.m_state == 0 && this.m_finishFunc) { - this.m_finishFunc(); - this.m_finishFunc = null; - } - }, - - checkAborted: function() { - if (this.m_exc) - throw this.m_exc; - }, - get isAborted() { return this.m_exc != null; }, - abort: function( exc ) { - if (!this.isAborted) // store only first error that has occurred - this.m_exc = exc; - if (this.m_abortFunc) { - this.m_abortFunc( exc ); - this.m_abortFunc = null; - } - } -}; +// calWcapSyncOperationListener.prototype.QueryInterface = +// function calWcapSyncOperationListener_QueryInterface(iid) { +// // xxx todo: +// const m_ifaces = [ Components.interfaces.nsISupports, +// Components.interfaces.calIOperationListener, +// Components.interfaces.calIWcapRequest ]; +// qiface(m_ifaces, iid); +// return this; +// }; -function FinishListener( opType, syncState ) { - this.wrappedJSObject = this; - this.m_opType = opType; - this.m_syncState = syncState; -} -FinishListener.prototype = { - m_opType: 0, - m_syncState: null, - - // calIOperationListener: - onOperationComplete: - function( calendar, status, opType, id, detail ) - { - if (status != Components.results.NS_OK) { - this.m_syncState.abort( detail ); - } - else if (this.m_opType != opType) { - this.m_syncState.abort( - new Components.Exception("unexpected operation type: " + - opType) ); - } - this.m_syncState.release(); - }, - onGetResult: - function( calendar, status, itemType, detail, count, items ) - { - this.m_syncState.abort( - new Components.Exception("unexpected onGetResult()!") ); - } -}; +// // calIOperationListener: +// calWcapSyncOperationListener.prototype.onOperationComplete = +// function calWcapSyncOperationListener_onOperationComplete( +// calendar, status, opType, id, detail) +// { +// if (status != Components.results.NS_OK) { +// this. +// this.m_syncState.abort( detail ); +// } +// else if (this.m_opType != opType) { +// this.m_syncState.abort( +// new Components.Exception("unexpected operation type: " + +// opType) ); +// } +// this.m_syncState.release(); +// }; -calWcapCalendar.prototype.syncChangesTo_resp = function( - wcapResponse, syncState, listener, func ) +// calWcapSyncOperationListener.prototype.onGetResult = +// function calWcapSyncOperationListener_onGetResult( +// calendar, status, itemType, detail, count, items) +// { +// this.m_syncState.abort( +// new Components.Exception("unexpected onGetResult()!") ); +// }; + +// calWcapCalendar.prototype.syncChangesTo_resp = function( +// wcapResponse, syncState, listener, func ) +// { +// try { +// var icalRootComp = wcapResponse.data; // first statement, may throw +// var items = this.parseItems_( +// function(items) { items.forEach(func) }, +// icalRootComp, +// calICalendar.ITEM_FILTER_ALL_ITEMS, +// 0, null, null ); +// } +// catch (exc) { +// syncState.abort( exc ); +// } +// syncState.release(); +// }; + +calWcapCalendar.prototype.syncChangesTo = +function calWcapCalendar_syncChangesTo(destCal, itemFilter, dtFrom_, listener) { - try { - var icalRootComp = wcapResponse.data; // first statement, may throw - var items = this.parseItems_( - function(items) { items.forEach(func) }, - icalRootComp, - Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS, - 0, null, null ); - } - catch (exc) { - syncState.abort( exc ); - } - syncState.release(); -}; - -calWcapCalendar.prototype.syncChangesTo_queued = function( - destCal, itemFilter, dtFrom_, listener ) -{ - const SYNC = Components.interfaces.calIWcapCalendar.SYNC; + var now = getTime(); // new stamp for this sync + var this_ = this; + var request_ = new calWcapRequest( + function syncChangesTo_resp(request, err) { + if (err) { + log("SYNC failed!", this_); + if (listener) { + listener.onOperationComplete( + this_.superCalendar, getResultCode(err), + calIWcapCalendar.SYNC, null, err); + } + if (getResultCode(err) != calIWcapErrors.WCAP_LOGIN_FAILED) { + this_.notifyError(err); + } + } + else { + log("SYNC succeeded.", this_); + if (listener) { + listener.onOperationComplete( + this_.superCalendar, Components.results.NS_OK, + calIWcapCalendar.SYNC, null, now); + } + } + }, + log("syncChangesTo():\n\titemFilter=0x" + itemFilter.toString(0x10) + + "\n\tdtFrom_=" + getIcalUTC(dtFrom_), this)); try { + // xxx todo: better thomas handles this... + // do NOT puke up error box every three minutes! + // again in a few minutes... + if (!this.session.isLoggedIn) { + throw new Components.Exception("Login failed. Invalid session ID.", + calIWcapErrors.WCAP_LOGIN_FAILED); + } + var dtFrom = dtFrom_; if (dtFrom) { dtFrom = dtFrom.clone(); - // assure DATETIMEs: + // assure DATE-TIME: if (dtFrom.isDate) dtFrom.isDate = false; dtFrom = this.session.getServerTime(dtFrom); } var zdtFrom = getIcalUTC(dtFrom); - this.log( "syncChangesTo():\n\titemFilter=0x" + itemFilter.toString(16)+ - "\n\tdtFrom=" + zdtFrom ); var calObserver = null; - try { - calObserver = listener.QueryInterface( - Components.interfaces.calIObserver ); - } - catch (exc) { + if (listener) { + try { + calObserver = listener.QueryInterface( + Components.interfaces.calIObserver); + } + catch (exc) { + } } - this.assureAccess(Components.interfaces.calIWcapCalendar.AC_COMP_READ); - - var this_ = this; - // new stamp for this sync: - var now = getTime(); - - var syncState = new SyncState( - // finishFunc: - function() { - if (listener) { - if (!syncState.isAborted) { - this_.log( "firing SYNC succeeded." ); - listener.onOperationComplete( - this_.superCalendar, Components.results.NS_OK, - SYNC, null, now ); + var request = new calWcapRequest( + function netFinishedRespFunc(err, data) { + var modifiedIds = {}; + for each (var item in request.m_modifiedItems) { + var dtCreated = item.getProperty("CREATED"); + var bAdd = (!dtCreated || !dtFrom || + dtCreated.compare(dtFrom) >= 0); + modifiedIds[item.id] = true; + if (bAdd) { + // xxx todo: verify whether exceptions + // have been written + log("syncChangesTo(): new item " + item.id, this_); + if (destCal) { +// destCal.addItem(item, addItemListener); + } + if (calObserver) + calObserver.onAddItem(item); + } + else { + log("syncChangesTo(): modified item " + item.id, this_); + if (destCal) { +// destCal.modifyItem(item, null, modifyItemListener); + } + if (calObserver) + calObserver.onModifyItem(item, null); } } - }, - // abortFunc: - function( exc ) { - if (listener) { - listener.onOperationComplete( - this_.superCalendar, Components.results.NS_OK, - SYNC, null, dtFrom_ /* pass original stamp: - => empty sync range */ ); -// listener.onOperationComplete( -// this_.superCalendar, -// Components.results.NS_ERROR_FAILURE, -// SYNC, null, exc ); + for each (var item in request.m_deletedItems) { + // don't delete anything that has been touched by lastmods: + if (modifiedIds[item.id]) + log("syncChangesTo(): skipping deletion of " + item.id, this_); + else if (isParent(item)) { + log("syncChangesTo(): deleted item " + item.id, this_); + if (destCal) { +// destCal.deleteItem(item, deleteItemListener); + } + if (calObserver) + calObserver.onDeleteItem(item); + } + else { // modify parent instead of + // straight-forward deleteItem(). WTF. + var parent = item.parentItem.clone(); + parent.recurrenceInfo.removeOccurrenceAt(item.recurrenceId); + log("syncChangesTo(): modified parent "+ parent.id, this_); + if (destCal) { +// destCal.modifyItem(parent, item, deleteItemListener); + } + if (calObserver) + calObserver.onModifyItem(parent, item); + } } - } ); + }, "syncChangesTo() netFinishedRespFunc"); + request_.attachSubRequest(request); - var addItemListener = new FinishListener( - Components.interfaces.calIOperationListener.ADD, syncState ); - var modifiedItems = []; - - this.log( "getting last modifications...", "syncChangesTo()" ); - var modifyItemListener = new FinishListener( - Components.interfaces.calIOperationListener.MODIFY, syncState ); var params = ("&relativealarm=1&compressed=1&recurring=1" + - "&fmt-out=text%2Fcalendar"); + "&emailorcalid=1&fmt-out=text%2Fcalendar"); params += ("&dtstart=" + zdtFrom); params += ("&dtend=" + getIcalUTC(this.session.getServerTime(now))); - syncState.acquire(); - this.session.issueAsyncRequest( - this.getCommandUrl("fetchcomponents_by_lastmod") + - params + getItemFilterUrlPortions(itemFilter), - stringToIcal, - function( wcapResponse ) { - this_.syncChangesTo_resp( - wcapResponse, syncState, listener, - function( item ) { - var dtCreated = item.getProperty("CREATED"); - var bAdd = (dtCreated == null || dtFrom == null || - dtCreated.compare(dtFrom) >= 0); - modifiedItems.push( item.id ); - if (bAdd) { - // xxx todo: verify whether exceptions - // have been written - this_.log( "new item: " + item.id, - "syncChangesTo_resp()" ); - if (destCal) { - syncState.acquire(); - destCal.addItem( item, addItemListener ); - } - if (calObserver) - calObserver.onAddItem( item ); - } - else { - this_.log( "modified item: " + item.id, - "syncChangesTo_resp()" ); - if (destCal) { - syncState.acquire(); - destCal.modifyItem( item, null, - modifyItemListener ); - } - if (calObserver) - calObserver.onModifyItem( item, null ); - } - } ); - } ); + log("syncChangesTo(): getting last modifications...", this); + this.issueNetworkRequest( + request, + function modifiedNetResp(err, icalRootComp) { + if (err) + throw err; + request.m_modifiedItems = this_.parseItems( + icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null); + }, + stringToIcal, "fetchcomponents_by_lastmod", + params + getItemFilterParams(itemFilter), + calIWcapCalendar.AC_COMP_READ); - this.log( "getting deleted items...", "syncChangesTo()" ); - var deleteItemListener = new FinishListener( - Components.interfaces.calIOperationListener.DELETE, syncState ); - syncState.acquire(); - this.session.issueAsyncRequest( - this.getCommandUrl("fetch_deletedcomponents") + params + - getItemFilterUrlPortions( itemFilter & // only component-type - Components.interfaces.calICalendar - .ITEM_FILTER_TYPE_ALL ), - stringToIcal, - function( wcapResponse ) { - this_.syncChangesTo_resp( - wcapResponse, syncState, listener, - function( item ) { - // don't delete anything that has been touched - // by lastmods: - if (modifiedItems.some( - function(mid) { return (item.id == mid); } )) { - this_.log( "skipping deletion of " + item.id, - "syncChangesTo_resp()" ); - return; - } - if (isParent(item)) { - this_.log( "deleted item: " + item.id, - "syncChangesTo_resp()" ); - if (destCal) { - syncState.acquire(); - destCal.deleteItem( - item, deleteItemListener ); - } - if (calObserver) - calObserver.onDeleteItem( item ); - } - else { - // modify parent instead of - // straight-forward deleteItem(). WTF. - var parent = item.parentItem.clone(); - parent.recurrenceInfo.removeOccurrenceAt( - item.recurrenceId ); - this_.log( "modified parent: " + parent.id, - "syncChangesTo_resp()" ); - if (destCal) { - syncState.acquire(); - destCal.modifyItem( parent, item, - deleteItemListener ); - } - if (calObserver) - calObserver.onModifyItem( parent, item ); - } - } ); - } ); + log("syncChangesTo(): getting deleted items...", this); + this.issueNetworkRequest( + request, + function modifiedNetResp(err, icalRootComp) { + if (err) + throw err; + request.m_deletedItems = this_.parseItems( + icalRootComp, calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null); + }, + stringToIcal, "fetch_deletedcomponents", + params + getItemFilterParams(itemFilter & // only component types + calICalendar.ITEM_FILTER_TYPE_ALL), + calIWcapCalendar.AC_COMP_READ); } catch (exc) { -// if (testResultCode(exc, Components.interfaces. -// calIWcapErrors.WCAP_LOGIN_FAILED)) { - // silently ignore login failed, no calIObserver UI, - // state everything ok and return empty range: - if (listener) { - listener.onOperationComplete( - this.superCalendar, Components.results.NS_OK, - SYNC, null, dtFrom_ /* pass original stamp: - => empty sync range */ ); - } - this.logError("ignored: " + errorToString(exc), "syncChangesTo()"); -// } -// else { -// if (listener) { -// listener.onOperationComplete( -// this.superCalendar, Components.results.NS_ERROR_FAILURE, -// SYNC, null, exc ); -// } -// this.notifyError( exc ); -// } + request_.execRespFunc(exc); } - this.log( "finished.", "syncChangesTo()" ); + return request_; }; diff --git a/calendar/providers/wcap/calWcapCalendarModule.js b/calendar/providers/wcap/calWcapCalendarModule.js index 569fafeda26..36678e57cd2 100644 --- a/calendar/providers/wcap/calWcapCalendarModule.js +++ b/calendar/providers/wcap/calWcapCalendarModule.js @@ -40,7 +40,7 @@ var g_ioService = null; function getIoService() { - if (g_ioService == null) { + if (!g_ioService) { g_ioService = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); } @@ -51,12 +51,20 @@ function getIoService() // init code for globals, prefs: // +// constants: +const nsIException = Components.interfaces.nsIException; +const calIWcapSession = Components.interfaces.calIWcapSession; +const calIWcapCalendar = Components.interfaces.calIWcapCalendar; +const calIWcapErrors = Components.interfaces.calIWcapErrors; +const calICalendar = Components.interfaces.calICalendar; +const calIOperationListener = Components.interfaces.calIOperationListener; + // ctors: var CalEvent; var CalTodo; var CalDateTime; var CalDuration; -var XmlHttpRequest; +var CalPeriod; var Timer; // some string resources: @@ -71,30 +79,48 @@ var CACHE = "off"; // denotes where to host local storage calendar(s) var CACHE_DIR = null; +// caching the last data retrievals: +var CACHE_LAST_RESULTS = 4; +// timer secs for invalidation: +var CACHE_LAST_RESULTS_INVALIDATE = 120; + // logging: -#expand var LOG_LEVEL = __LOG_LEVEL__; -var LOG_TIMEZONE = null; -var LOG_FILE_STREAM = null; +var LOG_LEVEL = 0; // whether alarms are by default turned on/off: var SUPPRESS_ALARMS = true; function initWcapProvider() { + try { + // xxx todo: hack + // the master password prompt is currently not guarded against + // multiple prompt; this initializes/raises the pw db at early stage. + var tokenDB = Components.classes["@mozilla.org/security/pk11tokendb;1"] + .getService(Components.interfaces.nsIPK11TokenDB); + var token = tokenDB.getInternalKeyToken(); + if (token.needsLogin && !token.needsUserInit) + token.login(false); + } + catch (exc) { + } + try { // ctors: - CalEvent = new Components.Constructor( - "@mozilla.org/calendar/event;1", "calIEvent" ); - CalTodo = new Components.Constructor( - "@mozilla.org/calendar/todo;1", "calITodo" ); - CalDateTime = new Components.Constructor( - "@mozilla.org/calendar/datetime;1", "calIDateTime" ); - CalDuration = new Components.Constructor( - "@mozilla.org/calendar/duration;1", "calIDuration" ); - XmlHttpRequest = new Components.Constructor( - "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest" ); - Timer = new Components.Constructor( - "@mozilla.org/timer;1", "nsITimer" ); + CalEvent = new Components.Constructor("@mozilla.org/calendar/event;1", + "calIEvent"); + CalTodo = new Components.Constructor("@mozilla.org/calendar/todo;1", + "calITodo"); + CalDateTime = new Components.Constructor("@mozilla.org/calendar/datetime;1", + "calIDateTime"); + CalDuration = new Components.Constructor("@mozilla.org/calendar/duration;1", + "calIDuration"); + CalPeriod = new Components.Constructor("@mozilla.org/calendar/period;1", + "calIPeriod"); + Timer = new Components.Constructor("@mozilla.org/timer;1", + "nsITimer"); + + initLogging(); // some string resources: g_privateItemTitle = getWcapBundle().GetStringFromName( @@ -105,50 +131,13 @@ function initWcapProvider() "busyItem.title.text"); g_busyPhantomItemUuidPrefix = ("PHANTOM_uuid" + getTime().icalString); - LOG_TIMEZONE = getPref("calendar.timezone.local", null); - - var logLevel = getPref("calendar.wcap.log_level", null); - if (logLevel == null) { // log_level pref undefined: - if (getPref("calendar.debug.log", false)) - logLevel = 1; // at least basic logging when calendar.debug.log - } - if (logLevel > LOG_LEVEL) { - LOG_LEVEL = logLevel; - } - - if (LOG_LEVEL > 0) { - var logFileName = getPref("calendar.wcap.log_file", null); - if (logFileName != null) { - // set up file: - var logFile = - Components.classes["@mozilla.org/file/local;1"] - .createInstance(Components.interfaces.nsILocalFile); - logFile.initWithPath( logFileName ); - // create output stream: - var logFileStream = Components.classes[ - "@mozilla.org/network/file-output-stream;1"] - .createInstance(Components.interfaces.nsIFileOutputStream); - logFileStream.init( - logFile, - 0x02 /* PR_WRONLY */ | - 0x08 /* PR_CREATE_FILE */ | - 0x10 /* PR_APPEND */, - 0700 /* read, write, execute/search by owner */, - 0 /* unused */ ); - LOG_FILE_STREAM = logFileStream; - } - logMessage( "init sequence", - "################################# NEW LOG " + - "#################################" ); - } - SUPPRESS_ALARMS = getPref("calendar.wcap.suppress_alarms", true); - logMessage("calendar.wcap.suppress_alarms", - SUPPRESS_ALARMS.toString()); + + CACHE_LAST_RESULTS = getPref("calendar.wcap.cache_last_results", 4); + CACHE_LAST_RESULTS_INVALIDATE = getPref("calendar.wcap.cache_last_results_invalidate", 120); // init cache dir directory: CACHE = getPref("calendar.wcap.cache", "off"); - logMessage( "calendar.wcap.cache", CACHE ); if (CACHE == "storage") { var cacheDir = null; var sCacheDir = getPref("calendar.wcap.cache_dir", null); @@ -158,15 +147,13 @@ function initWcapProvider() cacheDir.initWithPath( sCacheDir ); } else { // not found: default to wcap/ directory in profile - var dirService = Components.classes[ - "@mozilla.org/file/directory_service;1"] - .getService(Components.interfaces.nsIProperties); - cacheDir = dirService.get( - "ProfD", Components.interfaces.nsILocalFile ); + var dirService = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties); + cacheDir = dirService.get("ProfD", Components.interfaces.nsILocalFile); cacheDir.append( "wcap" ); } CACHE_DIR = cacheDir; - logMessage( "calendar.wcap.cache_dir", CACHE_DIR.path ); + log(CACHE_DIR.path, "cache dir"); if (!CACHE_DIR.exists()) { CACHE_DIR.create( Components.interfaces.nsIFile.DIRECTORY_TYPE, @@ -175,10 +162,21 @@ function initWcapProvider() } } catch (exc) { - logMessage( "error in init sequence", exc ); + logError(exc, "error in init sequence"); } } +var calWcapCalendarFactory = { // nsIFactory: + lockFactory: function calWcapCalendarFactory_lockFactory(lock) {}, + + createInstance: function calWcapCalendarFactory_createInstance(outer, iid) { + if (outer) + throw Components.results.NS_ERROR_NO_AGGREGATION; + var session = new calWcapSession(); + return session.defaultCalendar.QueryInterface(iid); + } +}; + var calWcapCalendarModule = { // nsIModule: WcapCalendarInfo: { @@ -193,80 +191,61 @@ var calWcapCalendarModule = { // nsIModule: classID: Components.ID("{CBF803FD-4469-4999-AE39-367AF1C7B077}") }, - registerSelf: - function( compMgr, fileSpec, location, type ) + registerSelf: function calWcapCalendarModule_registerSelf(compMgr, fileSpec, location, type) { - compMgr = compMgr.QueryInterface( - Components.interfaces.nsIComponentRegistrar ); - compMgr.registerFactoryLocation( - this.WcapCalendarInfo.classID, - this.WcapCalendarInfo.classDescription, - this.WcapCalendarInfo.contractID, - fileSpec, location, type ); - compMgr.registerFactoryLocation( - this.WcapSessionInfo.classID, - this.WcapSessionInfo.classDescription, - this.WcapSessionInfo.contractID, - fileSpec, location, type ); + compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + compMgr.registerFactoryLocation(this.WcapCalendarInfo.classID, + this.WcapCalendarInfo.classDescription, + this.WcapCalendarInfo.contractID, + fileSpec, location, type); + compMgr.registerFactoryLocation(this.WcapSessionInfo.classID, + this.WcapSessionInfo.classDescription, + this.WcapSessionInfo.contractID, + fileSpec, location, type); }, - unregisterSelf: - function( compMgr, fileSpec, location ) { - compMgr = compMgr.QueryInterface( - Components.interfaces.nsIComponentRegistrar ); - compMgr.unregisterFactoryLocation( - this.WcapCalendarInfo.classID, fileSpec ); - compMgr.unregisterFactoryLocation( - this.WcapSessionInfo.classID, fileSpec ); + unregisterSelf: function calWcapCalendarModule_unregisterSelf(compMgr, fileSpec, location) + { + compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + compMgr.unregisterFactoryLocation(this.WcapCalendarInfo.classID, fileSpec); + compMgr.unregisterFactoryLocation(this.WcapSessionInfo.classID, fileSpec); }, m_scriptsLoaded: false, - getClassObject: - function( compMgr, cid, iid ) + getClassObject: function calWcapCalendarModule_getClassObject(compMgr, cid, iid) { if (!this.m_scriptsLoaded) { // loading extra scripts from ../js: const scripts = [ "calWcapUtils.js", "calWcapErrors.js", "calWcapRequest.js", "calWcapSession.js", - "calWcapCalendar.js", "calWcapCalendarItems.js", - "calWcapCachedCalendar.js" ]; + "calWcapCalendar.js", "calWcapCalendarItems.js" ]; var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .createInstance(Components.interfaces.mozIJSSubScriptLoader); + .createInstance(Components.interfaces.mozIJSSubScriptLoader); var ioService = getIoService(); var baseDir = __LOCATION__.parent.parent; baseDir.append("js"); - for each ( var script in scripts ) { + for each (var script in scripts) { var scriptFile = baseDir.clone(); scriptFile.append(script); - scriptLoader.loadSubScript( - ioService.newFileURI(scriptFile).spec, null ); + scriptLoader.loadSubScript(ioService.newFileURI(scriptFile).spec, null); } initWcapProvider(); this.m_scriptsLoaded = true; } - if (!iid.equals( Components.interfaces.nsIFactory )) + if (!iid.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - if (!cid.equals( calWcapCalendar.prototype.classID )) + if (!cid.equals(calWcapCalendar.prototype.classID)) throw Components.results.NS_ERROR_NO_INTERFACE; - - return { // nsIFactory: - lockFactory: function( lock ) {}, - createInstance: function( outer, iid ) { - if (outer) - throw Components.results.NS_ERROR_NO_AGGREGATION; - var session = new calWcapSession(); - return session.defaultCalendar.QueryInterface(iid); - } - }; + return calWcapCalendarFactory; }, - canUnload: function( compMgr ) { return true; } + canUnload: function calWcapCalendarModule_canUnload(compMgr) { return true; } }; /** module export */ -function NSGetModule( compMgr, fileSpec ) { +function NSGetModule(compMgr, fileSpec) { return calWcapCalendarModule; } diff --git a/calendar/providers/wcap/calWcapErrors.js b/calendar/providers/wcap/calWcapErrors.js index 9d4f8c15ded..0f9a04799ca 100644 --- a/calendar/providers/wcap/calWcapErrors.js +++ b/calendar/providers/wcap/calWcapErrors.js @@ -51,7 +51,7 @@ function generateNetFailure(code) { return generateFailure(NS_ERROR_MODULE_NETWORK, code); } -function getErrorModule( rc ) { +function getErrorModule(rc) { return (((rc >>> 16) & 0x7fff) - NS_ERROR_MODULE_BASE_OFFSET); } @@ -138,99 +138,99 @@ function netErrorToString( rc ) const g_wcapErrorCodes = [ /* -1 */ Components.results.NS_OK, "Logout successful.", /* 0 */ Components.results.NS_OK, "Command successful.", - /* 1 */ Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED, "Login failed. Invalid session ID.", - /* 2 */ Components.interfaces.calIWcapErrors.WCAP_LOGIN_OK_DEFAULT_CALENDAR_NOT_FOUND, "login.wcap was successful, but the default calendar for this user was not found. A new default calendar set to the userid was created.", + /* 1 */ calIWcapErrors.WCAP_LOGIN_FAILED, "Login failed. Invalid session ID.", + /* 2 */ calIWcapErrors.WCAP_LOGIN_OK_DEFAULT_CALENDAR_NOT_FOUND, "login.wcap was successful, but the default calendar for this user was not found. A new default calendar set to the userid was created.", /* 3 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", /* 4 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", /* 5 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", - /* 6 */ Components.interfaces.calIWcapErrors.WCAP_DELETE_EVENTS_BY_ID_FAILED, "WCAP_DELETE_EVENTS_BY_ID_FAILED", + /* 6 */ calIWcapErrors.WCAP_DELETE_EVENTS_BY_ID_FAILED, "WCAP_DELETE_EVENTS_BY_ID_FAILED", /* 7 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", - /* 8 */ Components.interfaces.calIWcapErrors.WCAP_SETCALPROPS_FAILED, "WCAP_SETCALPROPS_FAILED", - /* 9 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_EVENTS_BY_ID_FAILED, "WCAP_FETCH_EVENTS_BY_ID_FAILED", - /* 10 */ Components.interfaces.calIWcapErrors.WCAP_CREATECALENDAR_FAILED, "WCAP_CREATECALENDAR_FAILED", - /* 11 */ Components.interfaces.calIWcapErrors.WCAP_DELETECALENDAR_FAILED, "WCAP_DELETECALENDAR_FAILED", - /* 12 */ Components.interfaces.calIWcapErrors.WCAP_ADDLINK_FAILED, "WCAP_ADDLINK_FAILED", - /* 13 */ Components.interfaces.calIWcapErrors.WCAP_FETCHBYDATERANGE_FAILED, "WCAP_FETCHBYDATERANGE_FAILED", - /* 14 */ Components.interfaces.calIWcapErrors.WCAP_STOREEVENTS_FAILED, "WCAP_STOREEVENTS_FAILED", - /* 15 */ Components.interfaces.calIWcapErrors.WCAP_STORETODOS_FAILED, "WCAP_STORETODOS_FAILED", - /* 16 */ Components.interfaces.calIWcapErrors.WCAP_DELETE_TODOS_BY_ID_FAILED, "WCAP_DELETE_TODOS_BY_ID_FAILED", - /* 17 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_TODOS_BY_ID_FAILED, "WCAP_FETCH_TODOS_BY_ID_FAILED", - /* 18 */ Components.interfaces.calIWcapErrors.WCAP_FETCHCOMPONENTS_FAILED_BAD_TZID, "Command failed to find correct tzid. Applies to fetchcomponents_by_range.wcap, fetchevents_by_id.wcap, fetchtodos_by_id.wcap.", - /* 19 */ Components.interfaces.calIWcapErrors.WCAP_SEARCH_CALPROPS_FAILED, "WCAP_SEARCH_CALPROPS_FAILED", - /* 20 */ Components.interfaces.calIWcapErrors.WCAP_GET_CALPROPS_FAILED, "WCAP_GET_CALPROPS_FAILED", - /* 21 */ Components.interfaces.calIWcapErrors.WCAP_DELETECOMPONENTS_BY_RANGE_FAILED, "WCAP_DELETECOMPONENTS_BY_RANGE_FAILED", - /* 22 */ Components.interfaces.calIWcapErrors.WCAP_DELETEEVENTS_BY_RANGE_FAILED, "WCAP_DELETEEVENTS_BY_RANGE_FAILED", - /* 23 */ Components.interfaces.calIWcapErrors.WCAP_DELETETODOS_BY_RANGE_FAILED, "WCAP_DELETETODOS_BY_RANGE_FAILED", - /* 24 */ Components.interfaces.calIWcapErrors.WCAP_GET_ALL_TIMEZONES_FAILED, "WCAP_GET_ALL_TIMEZONES_FAILED", - /* 25 */ Components.interfaces.calIWcapErrors.WCAP_CREATECALENDAR_ALREADY_EXISTS_FAILED, "The command createcalendar.wcap failed. A calendar with that name already exists in the database.", - /* 26 */ Components.interfaces.calIWcapErrors.WCAP_SET_USERPREFS_FAILED, "WCAP_SET_USERPREFS_FAILED", - /* 27 */ Components.interfaces.calIWcapErrors.WCAP_CHANGE_PASSWORD_FAILED, "WCAP_CHANGE_PASSWORD_FAILED", - /* 28 */ Components.interfaces.calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR, "Command failed. The user is denied access to a calendar.", - /* 29 */ Components.interfaces.calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST, "Command failed. The requested calendar does not exist in the database.", - /* 30 */ Components.interfaces.calIWcapErrors.WCAP_ILLEGAL_CALID_NAME, "createcalendar.wcap failed. Invalid calid passed in.", - /* 31 */ Components.interfaces.calIWcapErrors.WCAP_CANNOT_MODIFY_LINKED_EVENTS, "storeevents.wcap failed. The event to modify was a linked event.", - /* 32 */ Components.interfaces.calIWcapErrors.WCAP_CANNOT_MODIFY_LINKED_TODOS, "storetodos.wcap failed. The todo to modify was a linked todo.", - /* 33 */ Components.interfaces.calIWcapErrors.WCAP_CANNOT_SENT_EMAIL, "Command failed. Email notification failed. Usually caused by the server not being properly configured to send email. This can occur in storeevents.wcap, storetodos.wcap, deleteevents_by_id.wcap, deletetodos_by_id.wcap.", - /* 34 */ Components.interfaces.calIWcapErrors.WCAP_CALENDAR_DISABLED, "Command failed. The calendar is disabled in the database.", - /* 35 */ Components.interfaces.calIWcapErrors.WCAP_WRITE_IMPORT_FAILED, "Import failed when writing files to the server.", - /* 36 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_BY_LAST_MODIFIED_FAILED, "WCAP_FETCH_BY_LAST_MODIFIED_FAILED", - /* 37 */ Components.interfaces.calIWcapErrors.WCAP_CAPI_NOT_SUPPORTED, "Failed trying to read from unsupported format calendar data.", - /* 38 */ Components.interfaces.calIWcapErrors.WCAP_CALID_NOT_SPECIFIED, "Calendar ID was not specified.", - /* 39 */ Components.interfaces.calIWcapErrors.WCAP_GET_FREEBUSY_FAILED, "WCAP_GET_FREEBUSY_FAILED", - /* 40 */ Components.interfaces.calIWcapErrors.WCAP_STORE_FAILED_DOUBLE_BOOKED, "If double booking is not allowed in this calendar, storeevents.wcap fails with this error when attempting to store an event in a time slot that was already filled.", - /* 41 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_BY_ALARM_RANGE_FAILED, "WCAP_FETCH_BY_ALARM_RANGE_FAILED", - /* 42 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_BY_ATTENDEE_ERROR_FAILED, "WCAP_FETCH_BY_ATTENDEE_ERROR_FAILED", - /* 43 */ Components.interfaces.calIWcapErrors.WCAP_ATTENDEE_GROUP_EXPANSION_CLIPPED, "An LDAP group being expanded was too large and exceeded the maximum number allowed in an expansion. The expansion stopped at the specified maximum limit. The maximum limit defaults to 200. To change the maximum limit, set the server configuration preference calstore.group.attendee.maxsize.", - /* 44 */ Components.interfaces.calIWcapErrors.WCAP_USERPREFS_ACCESS_DENIED, "Either the server does not allow this administrator access to get or modify user preferences, or the requester is not an administrator.", - /* 45 */ Components.interfaces.calIWcapErrors.WCAP_NOT_ALLOWED_TO_REQUEST_PUBLISH, "The requester was not an organizer of the event, and, therefore, is not allowed to edit the component using the PUBLISH or REQUEST method.", - /* 46 */ Components.interfaces.calIWcapErrors.WCAP_INSUFFICIENT_PARAMETERS, "The caller tried to invoke verifyevents_by_ids.wcap, or verifytodos_by_ids.wcap with insufficient arguments (mismatched number of uid’s and rid’s).", - /* 47 */ Components.interfaces.calIWcapErrors.WCAP_MUSTBEOWNER_OPERATION, "The user needs to be an owner or co-owner of the calendar in questions to complete this operation. (Probably related to private or confidential component.)", - /* 48 */ Components.interfaces.calIWcapErrors.WCAP_DWP_CONNECTION_FAILED, "GSE scheduling engine failed to make connection to DWP.", - /* 49 */ Components.interfaces.calIWcapErrors.WCAP_DWP_MAX_CONNECTION_REACHED, "Reached the maximum number of connections. When some of the connections are freed, users can successfully connect. Same as error 11001.", - /* 50 */ Components.interfaces.calIWcapErrors.WCAP_DWP_CANNOT_RESOLVE_CALENDAR, "Front end can’t resolve to a particular back end. Same as error 11002.", - /* 51 */ Components.interfaces.calIWcapErrors.WCAP_DWP_BAD_DATA, "Generic response. Check all DWP servers. One might be down. Same as error 11003.", - /* 52 */ Components.interfaces.calIWcapErrors.WCAP_BAD_COMMAND, "The command sent in was not recognized. This is an internal only error code. It should not appear in the error logs.", - /* 53 */ Components.interfaces.calIWcapErrors.WCAP_NOT_FOUND, "Returned for all errors from a write to the Berkeley DB. This is an internal only error code. It should not appear in the error logs.", - /* 54 */ Components.interfaces.calIWcapErrors.WCAP_WRITE_IMPORT_CANT_EXPAND_CALID, "Can’t expand calid when importing file.", - /* 55 */ Components.interfaces.calIWcapErrors.WCAP_GETTIME_FAILED, "Get server time failed.", - /* 56 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_DELETEDCOMPONENTS_FAILED, "fetch_deletedcomponents.wcap failed.", - /* 57 */ Components.interfaces.calIWcapErrors.WCAP_FETCH_DELETEDCOMPONENTS_PARTIAL_RESULT, "Success but partial result.", - /* 58 */ Components.interfaces.calIWcapErrors.WCAP_WCAP_NO_SUCH_FORMAT, "Returned in any of the commands when supplied fmt-out is not a supported format.", - /* 59 */ Components.interfaces.calIWcapErrors.WCAP_COMPONENT_NOT_FOUND, "Returned when a fetch or delete is attempted that does not exist.", - /* 60 */ Components.interfaces.calIWcapErrors.WCAP_BAD_ARGUMENTS, "Currently used when attendee or organizer specified does not have a valid email address.", - /* 61 */ Components.interfaces.calIWcapErrors.WCAP_GET_USERPREFS_FAILED, "get_userprefs.wcap failed. The following error conditions returns error code 61: LDAP access denied, no results found, LDAP limit exceeded, LDAP connection failed.", - /* 62 */ Components.interfaces.calIWcapErrors.WCAP_WCAP_MODIFY_NO_EVENT, "storeevents.wcap issued with storetype set to 2 (WCAP_STORE_TYPE_MODIFY) and the event doesn\’t exist.", - /* 63 */ Components.interfaces.calIWcapErrors.WCAP_WCAP_CREATE_EXISTS, "storeevents.wcap issued with storetype set to 1 (WCAP_STORE_TYPE_CREATE) and the event already exists.", - /* 64 */ Components.interfaces.calIWcapErrors.WCAP_WCAP_MODIFY_CANT_MAKE_COPY, "storevents.wcap issued and copy of event failed during processing.", - /* 65 */ Components.interfaces.calIWcapErrors.WCAP_STORE_FAILED_RECUR_SKIP, "One instance of a recurring event skips over another.", - /* 66 */ Components.interfaces.calIWcapErrors.WCAP_STORE_FAILED_RECUR_SAMEDAY, "Two instances of a recurring event can’t occur on the same day.", - /* 67 */ Components.interfaces.calIWcapErrors.WCAP_BAD_ORG_ARGUMENTS, "Bad organizer arguments. orgCalid or orgEmail must be passed if any other \"org\" parameter is sent. That is, orgUID can’t be sent alone on a storeevents.wcap or a storetodos.wcao command if it is trying about to \"create\" the event or task. Note, if no \"org\" information is passed, the organizer defaults to the calid being passed with the command.", - /* 68 */ Components.interfaces.calIWcapErrors.WCAP_STORE_FAILED_RECUR_PRIVACY, "Error returned if you try to change the privacy or transparency of a single instance in a recurring series.", - /* 69 */ Components.interfaces.calIWcapErrors.WCAP_LDAP_ERROR, "For get_calprops.wcap, when there is an error is getting LDAP derived token values (X-S1CS-CALPROPS-FB-INCLUDE, X-S1CS-CALPROPS-COMMON-NAME).", - /* 70 */ Components.interfaces.calIWcapErrors.WCAP_GET_INVITE_COUNT_FAILED, "Error in getting invite count (for get_calprops.wcap and fetchcomponents_by_range.wcap commands).", - /* 71 */ Components.interfaces.calIWcapErrors.WCAP_LIST_FAILED, "list.wcap failed.", - /* 72 */ Components.interfaces.calIWcapErrors.WCAP_LIST_SUBSCRIBED_FAILED, "list_subscribed.wcap failed.", - /* 73 */ Components.interfaces.calIWcapErrors.WCAP_SUBSCRIBE_FAILED, "subscribe.wcap failed.", - /* 74 */ Components.interfaces.calIWcapErrors.WCAP_UNSUBSCRIBE_FAILED, "unsubscribe.wcap failed.", - /* 75 */ Components.interfaces.calIWcapErrors.WCAP_ANONYMOUS_NOT_ALLOWED, "Command cannot be executed as anonymous. Used only for list.wcap, list_subscribed.wcap, subscribe.wcap, and unsubscribe.wcap commands.", - /* 76 */ Components.interfaces.calIWcapErrors.WCAP_ACCESS_DENIED, "Generated if a non-administrator user tries to read or set the calendar-owned list or the calendar-subscribed list of some other user, or if the option is not turned on in the server.", - /* 77 */ Components.interfaces.calIWcapErrors.WCAP_BAD_IMPORT_ARGUMENTS, "Incorrect parameter received by import.wcap.", - /* 78 */ Components.interfaces.calIWcapErrors.WCAP_READONLY_DATABASE, "Database is in read-only mode (returned for all attempts to write to the database).", - /* 79 */ Components.interfaces.calIWcapErrors.WCAP_ATTENDEE_NOT_ALLOWED_TO_REQUEST_ON_MODIFY, "Attendee is not allowed to modify an event with method=request.", - /* 80 */ Components.interfaces.calIWcapErrors.WCAP_TRANSP_RESOURCE_NOT_ALLOWED, "Resources do not permit the transparency parameter.", - /* 81 */ Components.interfaces.calIWcapErrors.WCAP_RECURRING_COMPONENT_NOT_FOUND, "Recurring component not found. Only happens when recurring=1 is passed in by fetch commands. This code is returned if part of the recurring series (either the master or an exception) is missing.", + /* 8 */ calIWcapErrors.WCAP_SETCALPROPS_FAILED, "WCAP_SETCALPROPS_FAILED", + /* 9 */ calIWcapErrors.WCAP_FETCH_EVENTS_BY_ID_FAILED, "WCAP_FETCH_EVENTS_BY_ID_FAILED", + /* 10 */ calIWcapErrors.WCAP_CREATECALENDAR_FAILED, "WCAP_CREATECALENDAR_FAILED", + /* 11 */ calIWcapErrors.WCAP_DELETECALENDAR_FAILED, "WCAP_DELETECALENDAR_FAILED", + /* 12 */ calIWcapErrors.WCAP_ADDLINK_FAILED, "WCAP_ADDLINK_FAILED", + /* 13 */ calIWcapErrors.WCAP_FETCHBYDATERANGE_FAILED, "WCAP_FETCHBYDATERANGE_FAILED", + /* 14 */ calIWcapErrors.WCAP_STOREEVENTS_FAILED, "WCAP_STOREEVENTS_FAILED", + /* 15 */ calIWcapErrors.WCAP_STORETODOS_FAILED, "WCAP_STORETODOS_FAILED", + /* 16 */ calIWcapErrors.WCAP_DELETE_TODOS_BY_ID_FAILED, "WCAP_DELETE_TODOS_BY_ID_FAILED", + /* 17 */ calIWcapErrors.WCAP_FETCH_TODOS_BY_ID_FAILED, "WCAP_FETCH_TODOS_BY_ID_FAILED", + /* 18 */ calIWcapErrors.WCAP_FETCHCOMPONENTS_FAILED_BAD_TZID, "Command failed to find correct tzid. Applies to fetchcomponents_by_range.wcap, fetchevents_by_id.wcap, fetchtodos_by_id.wcap.", + /* 19 */ calIWcapErrors.WCAP_SEARCH_CALPROPS_FAILED, "WCAP_SEARCH_CALPROPS_FAILED", + /* 20 */ calIWcapErrors.WCAP_GET_CALPROPS_FAILED, "WCAP_GET_CALPROPS_FAILED", + /* 21 */ calIWcapErrors.WCAP_DELETECOMPONENTS_BY_RANGE_FAILED, "WCAP_DELETECOMPONENTS_BY_RANGE_FAILED", + /* 22 */ calIWcapErrors.WCAP_DELETEEVENTS_BY_RANGE_FAILED, "WCAP_DELETEEVENTS_BY_RANGE_FAILED", + /* 23 */ calIWcapErrors.WCAP_DELETETODOS_BY_RANGE_FAILED, "WCAP_DELETETODOS_BY_RANGE_FAILED", + /* 24 */ calIWcapErrors.WCAP_GET_ALL_TIMEZONES_FAILED, "WCAP_GET_ALL_TIMEZONES_FAILED", + /* 25 */ calIWcapErrors.WCAP_CREATECALENDAR_ALREADY_EXISTS_FAILED, "The command createcalendar.wcap failed. A calendar with that name already exists in the database.", + /* 26 */ calIWcapErrors.WCAP_SET_USERPREFS_FAILED, "WCAP_SET_USERPREFS_FAILED", + /* 27 */ calIWcapErrors.WCAP_CHANGE_PASSWORD_FAILED, "WCAP_CHANGE_PASSWORD_FAILED", + /* 28 */ calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR, "Command failed. The user is denied access to a calendar.", + /* 29 */ calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST, "Command failed. The requested calendar does not exist in the database.", + /* 30 */ calIWcapErrors.WCAP_ILLEGAL_CALID_NAME, "createcalendar.wcap failed. Invalid calid passed in.", + /* 31 */ calIWcapErrors.WCAP_CANNOT_MODIFY_LINKED_EVENTS, "storeevents.wcap failed. The event to modify was a linked event.", + /* 32 */ calIWcapErrors.WCAP_CANNOT_MODIFY_LINKED_TODOS, "storetodos.wcap failed. The todo to modify was a linked todo.", + /* 33 */ calIWcapErrors.WCAP_CANNOT_SENT_EMAIL, "Command failed. Email notification failed. Usually caused by the server not being properly configured to send email. This can occur in storeevents.wcap, storetodos.wcap, deleteevents_by_id.wcap, deletetodos_by_id.wcap.", + /* 34 */ calIWcapErrors.WCAP_CALENDAR_DISABLED, "Command failed. The calendar is disabled in the database.", + /* 35 */ calIWcapErrors.WCAP_WRITE_IMPORT_FAILED, "Import failed when writing files to the server.", + /* 36 */ calIWcapErrors.WCAP_FETCH_BY_LAST_MODIFIED_FAILED, "WCAP_FETCH_BY_LAST_MODIFIED_FAILED", + /* 37 */ calIWcapErrors.WCAP_CAPI_NOT_SUPPORTED, "Failed trying to read from unsupported format calendar data.", + /* 38 */ calIWcapErrors.WCAP_CALID_NOT_SPECIFIED, "Calendar ID was not specified.", + /* 39 */ calIWcapErrors.WCAP_GET_FREEBUSY_FAILED, "WCAP_GET_FREEBUSY_FAILED", + /* 40 */ calIWcapErrors.WCAP_STORE_FAILED_DOUBLE_BOOKED, "If double booking is not allowed in this calendar, storeevents.wcap fails with this error when attempting to store an event in a time slot that was already filled.", + /* 41 */ calIWcapErrors.WCAP_FETCH_BY_ALARM_RANGE_FAILED, "WCAP_FETCH_BY_ALARM_RANGE_FAILED", + /* 42 */ calIWcapErrors.WCAP_FETCH_BY_ATTENDEE_ERROR_FAILED, "WCAP_FETCH_BY_ATTENDEE_ERROR_FAILED", + /* 43 */ calIWcapErrors.WCAP_ATTENDEE_GROUP_EXPANSION_CLIPPED, "An LDAP group being expanded was too large and exceeded the maximum number allowed in an expansion. The expansion stopped at the specified maximum limit. The maximum limit defaults to 200. To change the maximum limit, set the server configuration preference calstore.group.attendee.maxsize.", + /* 44 */ calIWcapErrors.WCAP_USERPREFS_ACCESS_DENIED, "Either the server does not allow this administrator access to get or modify user preferences, or the requester is not an administrator.", + /* 45 */ calIWcapErrors.WCAP_NOT_ALLOWED_TO_REQUEST_PUBLISH, "The requester was not an organizer of the event, and, therefore, is not allowed to edit the component using the PUBLISH or REQUEST method.", + /* 46 */ calIWcapErrors.WCAP_INSUFFICIENT_PARAMETERS, "The caller tried to invoke verifyevents_by_ids.wcap, or verifytodos_by_ids.wcap with insufficient arguments (mismatched number of uid’s and rid’s).", + /* 47 */ calIWcapErrors.WCAP_MUSTBEOWNER_OPERATION, "The user needs to be an owner or co-owner of the calendar in questions to complete this operation. (Probably related to private or confidential component.)", + /* 48 */ calIWcapErrors.WCAP_DWP_CONNECTION_FAILED, "GSE scheduling engine failed to make connection to DWP.", + /* 49 */ calIWcapErrors.WCAP_DWP_MAX_CONNECTION_REACHED, "Reached the maximum number of connections. When some of the connections are freed, users can successfully connect. Same as error 11001.", + /* 50 */ calIWcapErrors.WCAP_DWP_CANNOT_RESOLVE_CALENDAR, "Front end can’t resolve to a particular back end. Same as error 11002.", + /* 51 */ calIWcapErrors.WCAP_DWP_BAD_DATA, "Generic response. Check all DWP servers. One might be down. Same as error 11003.", + /* 52 */ calIWcapErrors.WCAP_BAD_COMMAND, "The command sent in was not recognized. This is an internal only error code. It should not appear in the error logs.", + /* 53 */ calIWcapErrors.WCAP_NOT_FOUND, "Returned for all errors from a write to the Berkeley DB. This is an internal only error code. It should not appear in the error logs.", + /* 54 */ calIWcapErrors.WCAP_WRITE_IMPORT_CANT_EXPAND_CALID, "Can’t expand calid when importing file.", + /* 55 */ calIWcapErrors.WCAP_GETTIME_FAILED, "Get server time failed.", + /* 56 */ calIWcapErrors.WCAP_FETCH_DELETEDCOMPONENTS_FAILED, "fetch_deletedcomponents.wcap failed.", + /* 57 */ calIWcapErrors.WCAP_FETCH_DELETEDCOMPONENTS_PARTIAL_RESULT, "Success but partial result.", + /* 58 */ calIWcapErrors.WCAP_WCAP_NO_SUCH_FORMAT, "Returned in any of the commands when supplied fmt-out is not a supported format.", + /* 59 */ calIWcapErrors.WCAP_COMPONENT_NOT_FOUND, "Returned when a fetch or delete is attempted that does not exist.", + /* 60 */ calIWcapErrors.WCAP_BAD_ARGUMENTS, "Currently used when attendee or organizer specified does not have a valid email address.", + /* 61 */ calIWcapErrors.WCAP_GET_USERPREFS_FAILED, "get_userprefs.wcap failed. The following error conditions returns error code 61: LDAP access denied, no results found, LDAP limit exceeded, LDAP connection failed.", + /* 62 */ calIWcapErrors.WCAP_WCAP_MODIFY_NO_EVENT, "storeevents.wcap issued with storetype set to 2 (WCAP_STORE_TYPE_MODIFY) and the event doesn\’t exist.", + /* 63 */ calIWcapErrors.WCAP_WCAP_CREATE_EXISTS, "storeevents.wcap issued with storetype set to 1 (WCAP_STORE_TYPE_CREATE) and the event already exists.", + /* 64 */ calIWcapErrors.WCAP_WCAP_MODIFY_CANT_MAKE_COPY, "storevents.wcap issued and copy of event failed during processing.", + /* 65 */ calIWcapErrors.WCAP_STORE_FAILED_RECUR_SKIP, "One instance of a recurring event skips over another.", + /* 66 */ calIWcapErrors.WCAP_STORE_FAILED_RECUR_SAMEDAY, "Two instances of a recurring event can’t occur on the same day.", + /* 67 */ calIWcapErrors.WCAP_BAD_ORG_ARGUMENTS, "Bad organizer arguments. orgCalid or orgEmail must be passed if any other \"org\" parameter is sent. That is, orgUID can’t be sent alone on a storeevents.wcap or a storetodos.wcao command if it is trying about to \"create\" the event or task. Note, if no \"org\" information is passed, the organizer defaults to the calid being passed with the command.", + /* 68 */ calIWcapErrors.WCAP_STORE_FAILED_RECUR_PRIVACY, "Error returned if you try to change the privacy or transparency of a single instance in a recurring series.", + /* 69 */ calIWcapErrors.WCAP_LDAP_ERROR, "For get_calprops.wcap, when there is an error is getting LDAP derived token values (X-S1CS-CALPROPS-FB-INCLUDE, X-S1CS-CALPROPS-COMMON-NAME).", + /* 70 */ calIWcapErrors.WCAP_GET_INVITE_COUNT_FAILED, "Error in getting invite count (for get_calprops.wcap and fetchcomponents_by_range.wcap commands).", + /* 71 */ calIWcapErrors.WCAP_LIST_FAILED, "list.wcap failed.", + /* 72 */ calIWcapErrors.WCAP_LIST_SUBSCRIBED_FAILED, "list_subscribed.wcap failed.", + /* 73 */ calIWcapErrors.WCAP_SUBSCRIBE_FAILED, "subscribe.wcap failed.", + /* 74 */ calIWcapErrors.WCAP_UNSUBSCRIBE_FAILED, "unsubscribe.wcap failed.", + /* 75 */ calIWcapErrors.WCAP_ANONYMOUS_NOT_ALLOWED, "Command cannot be executed as anonymous. Used only for list.wcap, list_subscribed.wcap, subscribe.wcap, and unsubscribe.wcap commands.", + /* 76 */ calIWcapErrors.WCAP_ACCESS_DENIED, "Generated if a non-administrator user tries to read or set the calendar-owned list or the calendar-subscribed list of some other user, or if the option is not turned on in the server.", + /* 77 */ calIWcapErrors.WCAP_BAD_IMPORT_ARGUMENTS, "Incorrect parameter received by import.wcap.", + /* 78 */ calIWcapErrors.WCAP_READONLY_DATABASE, "Database is in read-only mode (returned for all attempts to write to the database).", + /* 79 */ calIWcapErrors.WCAP_ATTENDEE_NOT_ALLOWED_TO_REQUEST_ON_MODIFY, "Attendee is not allowed to modify an event with method=request.", + /* 80 */ calIWcapErrors.WCAP_TRANSP_RESOURCE_NOT_ALLOWED, "Resources do not permit the transparency parameter.", + /* 81 */ calIWcapErrors.WCAP_RECURRING_COMPONENT_NOT_FOUND, "Recurring component not found. Only happens when recurring=1 is passed in by fetch commands. This code is returned if part of the recurring series (either the master or an exception) is missing.", /* new by WCAP 4.0: */ - /* 82 */ Components.interfaces.calIWcapErrors.WCAP_BAD_MIME_TYPE, + /* 82 */ calIWcapErrors.WCAP_BAD_MIME_TYPE, "The mime headers supplied while storing the attachment using storeevents.wcap/storetodos.wcap is malformatted.", - /* 83 */ Components.interfaces.calIWcapErrors.WCAP_MISSING_BOUNDARY, + /* 83 */ calIWcapErrors.WCAP_MISSING_BOUNDARY, "While supplying attachments to the storeveents/storetodos commands the mime boundary was not found.", - /* 84 */ Components.interfaces.calIWcapErrors.WCAP_INVALID_ATTACHMENT, + /* 84 */ calIWcapErrors.WCAP_INVALID_ATTACHMENT, "The attachment supplied to be stored on the server is malformatted.", - /* 85 */ Components.interfaces.calIWcapErrors.WCAP_ATTACH_DELETE_SUCCESS, + /* 85 */ calIWcapErrors.WCAP_ATTACH_DELETE_SUCCESS, "All the attachments requested to be deleted from the server by supplying deleteattach were deleted successsfully.", - /* 86 */ Components.interfaces.calIWcapErrors.WCAP_ATTACH_DELETE_PARTIAL, + /* 86 */ calIWcapErrors.WCAP_ATTACH_DELETE_PARTIAL, "Of All attachments requested to be deleted from the server by supplying deleteattach , only few were deleted successfully.", - /* 87 */ Components.interfaces.calIWcapErrors.WCAP_ATTACHMENT_NOT_FOUND, + /* 87 */ calIWcapErrors.WCAP_ATTACHMENT_NOT_FOUND, "The attachent requested to be fetched or deleted from the server was not found.", /* / new by WCAP 4.0 */ /* 88 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", @@ -245,25 +245,25 @@ const g_wcapErrorCodes = [ /* 97 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", /* 98 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", /* 99 */ Components.results.NS_ERROR_INVALID_ARG, "No WCAP error code.", - /* 11000 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_MAX_CONNECTION_REACHED, "Maximum connections to back-end database reached. As connections are freed up, users can connect to the back-end.", - /* 11001 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_CANNOT_CONNECT, "Cannot connect to back-end server. Back-end machine might be down or DWP server is not up and running.", - /* 11002 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_CANNOT_RESOLVE_CALENDAR, "Front-end can’t resolve calendar to a particular back-end server.", - /* 11003 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_BAD_DATA, "Bad data received from DWP connection. This is a generic formatting error. Check all DWP servers. One might be down.", - /* 11004 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_DWPHOST_CTX_DOES_NOT_EXIST, "For the back-end host, context doesn\’t exist in the context table.", - /* 11005 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_HOSTNAME_NOT_RESOLVABLE, "DNS or NIS files, or hostname resolver is not set up properly or machine does not exist.", - /* 11006 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_NO_DATA, "No data was received from reading the calendar properties from the DWP connection.", - /* 11007 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_AUTH_FAILED, "DWP authentication failed.", - /* 11008 */ Components.interfaces.calIWcapErrors.WCAP_CDWP_ERR_CHECKVERSION_FAILED, "DWP version check failed." + /* 11000 */ calIWcapErrors.WCAP_CDWP_ERR_MAX_CONNECTION_REACHED, "Maximum connections to back-end database reached. As connections are freed up, users can connect to the back-end.", + /* 11001 */ calIWcapErrors.WCAP_CDWP_ERR_CANNOT_CONNECT, "Cannot connect to back-end server. Back-end machine might be down or DWP server is not up and running.", + /* 11002 */ calIWcapErrors.WCAP_CDWP_ERR_CANNOT_RESOLVE_CALENDAR, "Front-end can’t resolve calendar to a particular back-end server.", + /* 11003 */ calIWcapErrors.WCAP_CDWP_ERR_BAD_DATA, "Bad data received from DWP connection. This is a generic formatting error. Check all DWP servers. One might be down.", + /* 11004 */ calIWcapErrors.WCAP_CDWP_ERR_DWPHOST_CTX_DOES_NOT_EXIST, "For the back-end host, context doesn\’t exist in the context table.", + /* 11005 */ calIWcapErrors.WCAP_CDWP_ERR_HOSTNAME_NOT_RESOLVABLE, "DNS or NIS files, or hostname resolver is not set up properly or machine does not exist.", + /* 11006 */ calIWcapErrors.WCAP_CDWP_ERR_NO_DATA, "No data was received from reading the calendar properties from the DWP connection.", + /* 11007 */ calIWcapErrors.WCAP_CDWP_ERR_AUTH_FAILED, "DWP authentication failed.", + /* 11008 */ calIWcapErrors.WCAP_CDWP_ERR_CHECKVERSION_FAILED, "DWP version check failed." ]; function wcapErrorToString( rc ) { if (isNaN(rc)) throw Components.results.NS_ERROR_INVALID_ARG; - if (rc == Components.interfaces.calIWcapErrors.WCAP_NO_ERRNO) + if (rc == calIWcapErrors.WCAP_NO_ERRNO) return "No WCAP errno (missing X-NSCP-WCAP-ERRNO)."; - var index = (rc - Components.interfaces.calIWcapErrors.WCAP_ERROR_BASE + 1); + var index = (rc - calIWcapErrors.WCAP_ERROR_BASE + 1); if (index >= 1 && index <= 108 && g_wcapErrorCodes[index * 2] != Components.results.NS_ERROR_INVALID_ARG) { @@ -272,7 +272,7 @@ function wcapErrorToString( rc ) throw Components.results.NS_ERROR_INVALID_ARG; } -function getWcapErrorCode( errno ) +function getWcapErrorCode(errno) { var index = -1; if (errno >= -1 && errno <= 81) @@ -287,80 +287,112 @@ function getWcapErrorCode( errno ) throw Components.results.NS_ERROR_INVALID_ARG; } -function getWcapXmlErrno( xml ) +function getWcapXmlErrno(xml) { - var elem = xml.getElementsByTagName( "X-NSCP-WCAP-ERRNO" ); + var elem = xml.getElementsByTagName("X-NSCP-WCAP-ERRNO"); if (elem) { elem = elem.item(0); if (elem) return parseInt(elem.textContent); } // some commands just respond with an empty calendar, no errno. WTF. - throw new Components.Exception( - "No WCAP errno (missing X-NSCP-WCAP-ERRNO).", - Components.interfaces.calIWcapErrors.WCAP_NO_ERRNO ); + // assume success: + return undefined; } -function getWcapIcalErrno( icalRootComp ) +function getWcapIcalErrno(icalRootComp) { - var prop = icalRootComp.getFirstProperty( "X-NSCP-WCAP-ERRNO" ); + var prop = icalRootComp.getFirstProperty("X-NSCP-WCAP-ERRNO"); if (prop) return parseInt(prop.value); // some commands just respond with an empty calendar, no errno. WTF. - throw new Components.Exception( - "No WCAP errno (missing X-NSCP-WCAP-ERRNO).", - Components.interfaces.calIWcapErrors.WCAP_NO_ERRNO ); + // assume success: + return undefined; } -function checkWcapErrno( errno, expectedErrno ) +function checkWcapErrno(errno, expectedErrno) { - if (expectedErrno == undefined) + if (expectedErrno === undefined) expectedErrno = 0; // i.e. Command successful. - if (errno != expectedErrno) { + if (errno !== undefined && errno != expectedErrno) { var rc; try { rc = getWcapErrorCode(errno); } catch (exc) { throw new Components.Exception( - "No WCAP error no.", Components.results.NS_ERROR_INVALID_ARG ); + "No WCAP error no.", Components.results.NS_ERROR_INVALID_ARG); } - throw new Components.Exception( wcapErrorToString(rc), rc ); + throw new Components.Exception(wcapErrorToString(rc), rc); } } -function checkWcapXmlErrno( xml, expectedErrno ) -{ - checkWcapErrno( getWcapXmlErrno(xml), expectedErrno ); +function checkWcapXmlErrno(xml, expectedErrno) { + checkWcapErrno(getWcapXmlErrno(xml), expectedErrno); } -function checkWcapIcalErrno( icalRootComp, expectedErrno ) -{ - checkWcapErrno( getWcapIcalErrno(icalRootComp), expectedErrno ); +function checkWcapIcalErrno(icalRootComp, expectedErrno) { + checkWcapErrno(getWcapIcalErrno(icalRootComp), expectedErrno); } - -function errorToString( err ) +function getResultCode(err) { - if (typeof(err) == "string") - return err; - if (err instanceof Error) - return err.message; - if (err instanceof Components.interfaces.nsIException) - return err.toString(); // xxx todo: or just message? + if (err === undefined || err === null) + return Components.results.NS_OK; + if (isNaN(err)) { + if (err instanceof nsIException) + return err.result; + else + return Components.results.NS_ERROR_FAILURE; + } + return err; +} + +function errorToString(err) +{ + if (err) { + if (typeof(err) == "string") + return err; + if (err instanceof Error) + return err.message; + if (err instanceof nsIException) + return err.toString(); // xxx todo: or just message? + if (isNaN(err)) + return ("[" + err + "] unknown error."); + } // numeric codes: switch (err) { + case undefined: + case null: + case Components.results.NS_OK: + return "NS_OK"; case Components.results.NS_ERROR_INVALID_ARG: return "NS_ERROR_INVALID_ARG"; case Components.results.NS_ERROR_NO_INTERFACE: return "NS_ERROR_NO_INTERFACE"; case Components.results.NS_ERROR_NOT_IMPLEMENTED: return "NS_ERROR_NOT_IMPLEMENTED"; + case Components.results.NS_ERROR_NOT_AVAILABLE: + return "NS_ERROR_NOT_AVAILABLE"; case Components.results.NS_ERROR_FAILURE: return "NS_ERROR_FAILURE"; + case Components.results.NS_ERROR_BASE: + return "NS_ERROR_BASE"; + case Components.results.NS_ERROR_NOT_INITIALIZED: + return "NS_ERROR_NOT_INITIALIZED"; + case Components.results.NS_ERROR_ALREADY_INITIALIZED: + return "NS_ERROR_ALREADY_INITIALIZED"; + case Components.results.NS_ERROR_NULL_POINTER: + return "NS_ERROR_NULL_POINTER"; + case Components.results.NS_ERROR_ABORT: + return "NS_ERROR_ABORT"; + case Components.results.NS_ERROR_UNEXPECTED: + return "NS_ERROR_UNEXPECTED"; + case Components.results.NS_ERROR_OUT_OF_MEMORY: + return "NS_ERROR_OUT_OF_MEMORY"; + case Components.results.NS_ERROR_ILLEGAL_VALUE: + return "NS_ERROR_ILLEGAL_VALUE"; default: - if (isNaN(err)) - return ("[" + err + "] Unknown error."); // probe for WCAP error: try { return wcapErrorToString(err); @@ -370,7 +402,7 @@ function errorToString( err ) return netErrorToString(err); } catch (exc) { - return ("[0x" + err.toString(16) + "] Unknown error."); + return ("[0x" + err.toString(0x10) + "] unknown error."); } } } diff --git a/calendar/providers/wcap/calWcapRequest.js b/calendar/providers/wcap/calWcapRequest.js index 63cd5710740..85e396849b5 100644 --- a/calendar/providers/wcap/calWcapRequest.js +++ b/calendar/providers/wcap/calWcapRequest.js @@ -37,9 +37,356 @@ * * ***** END LICENSE BLOCK ***** */ -// -// WCAP request helpers -// +/** + Requests, either the queued calWcapRequest or an async network request. + + A request object is used to track an async action. + While the action is running, isPending is true. + Functions issuing an async action usually take a response function along + with their parameters, typically named respFunc. + That function is called *after* the action has ended (i.e. isPending of the + issued action/request is false when called, status remains stable). + The response function gets the ended request as first parameter to check + whether the request has been successful and get its data. + The request function itself may return either + - a further calIWcapRequest request object, i.e. an async continuation + - some data (incl null/undefined) which is the result of the async function, + indicating that there is no further continuation +*/ + +var g_requestId = 0; +function generateRequestId() { + ++g_requestId; + return g_requestId; +} + +function calWcapRequest(respFunc, logContext) { + this.wrappedJSObject = this; + this.m_logContext = logContext; + this.m_id = generateRequestId(); + this.m_isPending = true; + this.m_status = Components.results.NS_OK; + this.m_respFunc = respFunc; + this.m_attachedRequests = []; +} +calWcapRequest.prototype = { + m_logContext: null, + m_parentRequest: null, + m_id: 0, + m_isPending: true, + m_status: Components.results.NS_OK, + m_respFunc: null, + m_attachedRequests: null, + m_locked: false, + + get parentRequest() { return this.m_parentRequest; }, + set parentRequest(req) { + if (this.parentRequest) + logError("already has parent!", this); + this.detachFromParent(); // detach without error + this.m_parentRequest = req; + }, + + /** The following locking is necessary when scheduling multiple async + requests; one cannot be sure that e.g. the first completes quickly + and responds the whole parent request when detaching. + */ + lockPending: function calWcapRequest_lockPending() { + this.m_locked = true; + }, + unlockPending: function calWcapRequest_unlockPending() { + if (this.m_locked) { + this.m_locked = false; + // assures that respFunc is executed: + if (this.m_attachedRequests.length == 0) { + this.execRespFunc(); + } + } + }, + + toString: function calWcapRequest_toString() { + var ret = ("calWcapRequest id=" + this.id + + ", parent-id=" + + (this.parentRequest ? this.parentRequest.id : "") + + " (" + this.m_logContext + ")"); + if (LOG_LEVEL > 2 && this.m_attachedRequests.length > 0) { + ret += "\nattached requests:"; + for each ( var req in this.m_attachedRequests ) { + ret += ("\n#" + req.id + "\t" + req); + } + } + return ret; + }, + + attachSubRequest: function calWcapRequest_attachSubRequest(req) + { + if (req) { + if (!this.m_attachedRequests.some( + function(req_) { return req.id == req_.id; } )) { + if (req.isPending) { + this.m_attachedRequests.push(req); + req.parentRequest = this; + log("attachSubRequest()", this); + } + else if (!this.m_locked && this.m_attachedRequests.length == 0) { + this.execRespFunc(req.status); + } + } + else { + logError("request already attached: " + req.id, this); + } + } + }, + + detachSubRequest: function calWcapRequest_detachSubRequest(req, err) + { + this.m_attachedRequests = this.m_attachedRequests.filter( + function(req_) { return req.id != req_.id; } ); + if (err) { + // first failing sub request stops whole request: + this.execRespFunc(err); + } + // assures that respFunc is executed after every sub request has been completed: + else if (!this.m_locked && this.m_attachedRequests.length == 0) { + this.execRespFunc(); + } + }, + + cancelAllSubRequests: function calWcapRequest_cancelAllSubRequests(status) { + var attachedRequests = this.m_attachedRequests; + this.m_attachedRequests = []; + attachedRequests.forEach( function(req) { req.cancel(status); } ); + }, + + detachFromParent: function calWcapRequest_detachFromParent(err) { + var parentRequest = this.m_parentRequest; + if (parentRequest) { + this.m_parentRequest = null; + parentRequest.detachSubRequest(this, err); + } + }, + + execRespFunc: function calWcapRequest_execRespFunc(err, data) + { + if (this.isPending) { + this.m_isPending = false; + if (err) + this.m_status = err; + this.cancelAllSubRequests(); + var respFunc = this.m_respFunc; + if (respFunc) { + this.m_respFunc = null; // call once only + if (LOG_LEVEL > 2) { + log("response exec: " + errorToString(err), this); + } + try { + respFunc(this, err, data); + } + catch (exc) { + this.m_status = exc; + logError(exc, this); + } + } + this.detachFromParent(this.m_status); + } + }, + + // calIWcapRequest: + get id() { + return this.m_id; + }, + get isPending() { + return this.m_isPending; + }, + get succeeded() { + return (!this.isPending && + getResultCode(this.status) == Components.results.NS_OK); + }, + get status() { + return (this.m_status === null ? Components.results.NS_OK + : this.m_status); + }, + + cancel: function calWcapRequest_cancel(status) + { + if (this.isPending) { + this.m_isPending = false; + log("cancel.", this); + this.m_respFunc = null; + if (!status) + status = Components.results.NS_ERROR_FAILURE; + this.m_status = status; + this.cancelAllSubRequests(); + this.detachFromParent(); // detach without error + } + } +}; + +function calWcapNetworkRequest(channel, respFunc, bLogging) { +// this.superClass(respFunc); + this.wrappedJSObject = this; + this.m_id = generateRequestId(); + this.m_channel = channel; + this.m_respFunc = respFunc; + this.m_bLogging = (bLogging === undefined ? true : bLogging); +} +// subClass(calWcapNetworkRequest, calWcapRequest); + +calWcapNetworkRequest.prototype = { + m_id: 0, + m_channel: null, + m_respFunc: null, + m_bLogging: false, + + m_isPending: true, + get isPending() { return this.m_isPending; }, + + toString: function calWcapNetworkRequest_toString() { + var ret = ("calWcapNetworkRequest id=" + this.id + + ", parent-id=" + + (this.parentRequest ? this.parentRequest.id : "")); + if (this.m_bLogging) + ret += (" (" + this.m_channel.URI.spec + ")"); + return ret; + }, + + m_parentRequest: null, + get parentRequest() { return this.m_parentRequest; }, + set parentRequest(req) { + if (this.parentRequest) + logError("already has parent!", this); + this.detachFromParent(); // detach without error + this.m_parentRequest = req; + }, + + get id() { + return this.m_id; + }, + + detachFromParent: function calWcapNetworkRequest_detachFromParent(err) { + var parentRequest = this.m_parentRequest; + if (parentRequest) { + this.m_parentRequest = null; + parentRequest.detachSubRequest(this, err); + } + }, + + cancel: function calWcapNetworkRequest_cancel(status) { + if (this.isPending) { + this.m_isPending = false; + log("cancel.", this); + this.m_respFunc = null; + this.detachFromParent(); // detach without error + // xxx todo: check whether this works on redirected channels! + if (this.m_channel.isPending()) { + log("cancelling netwerk request...", this); + this.m_channel.cancel(NS_BINDING_FAILED); + } + } + }, + + execRespFunc: function calWcapNetworkRequest_execRespFunc(err, str) + { + if (this.isPending) { + this.m_isPending = false; + var respFunc = this.m_respFunc; + if (respFunc) { + this.m_respFunc = null; // call once only + if (LOG_LEVEL > 2 && this.m_bLogging) { + log("response exec: " + errorToString(err), this); + } + try { + respFunc(err, str); + err = null; // may have been handled + } + catch (exc) { + logError(exc, this); + err = exc; + } + } + this.detachFromParent(err); + } + }, + + // nsIUnicharStreamLoaderObserver: + onDetermineCharset: function calWcapNetworkRequest_onDetermineCharset( + loader, context, firstSegment, length) + { + var channel = null; + if (loader) + channel = loader.channel; + var charset = null; + if (channel) + charset = channel.contentCharset; + if (!charset || charset.length == 0) + charset = "UTF-8"; + return charset; + }, + + onStreamComplete: function calWcapNetworkRequest_onStreamComplete( + loader, context, status, /* nsIUnicharInputStream */ unicharData) + { + if (LOG_LEVEL > 0 && this.m_bLogging) { + log("status: " + errorToString(status), this); + } + switch (status) { + case NS_BINDING_SUCCEEDED: { + var err = null; + var str = ""; + try { + if (unicharData) { + var str_ = {}; + while (unicharData.readString(-1, str_)) { + str += str_.value; + } + } + if (LOG_LEVEL > 2 && this.m_bLogging) { + log("contentCharset = " + this.onDetermineCharset(loader) + + "\nrequest result:\n" + str, this); + } + } + catch (exc) { + err = exc; + } + this.execRespFunc(err, str); + break; + } + case NS_BINDING_REDIRECTED: + case NS_BINDING_RETARGETED: + // just status + // xxx todo: in case of a redirected channel, + // how to get that channel => cancel feature! + break; + default: // errors: + this.execRespFunc(status); + break; + } + } +}; + +function issueNetworkRequest(parentRequest, respFunc, url, bLogging) +{ + var channel; + try { + var loader = Components.classes["@mozilla.org/network/unichar-stream-loader;1"] + .createInstance(Components.interfaces.nsIUnicharStreamLoader); + channel = getIoService().newChannel(url, "" /* charset */, null /* baseURI */); + channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + } + catch (exc) { + respFunc(exc); + return; + } + var netRequest = new calWcapNetworkRequest(channel, respFunc, bLogging); + parentRequest.attachSubRequest(netRequest); + log("opening channel.", netRequest); + try { + loader.init(channel, netRequest, null /*context*/, 0 /*segment size*/); + } + catch (exc) { + netRequest.execRespFunc(exc); + } +} function getWcapRequestStatusString( xml ) { @@ -52,249 +399,34 @@ function getWcapRequestStatusString( xml ) return str; } -// response object for Calendar.issueRequest() -function WcapResponse() { -} -WcapResponse.prototype = { - m_response: null, - m_exc: null, - - get data() { - if (this.m_exc != null) { - // clear exception, so it is not thrown again on sync requests: - var exc = this.m_exc; - this.m_exc = null; - throw exc; - } - return this.m_data; - }, - set data(d) { - this.m_data = d; - this.m_exc = null; - }, - get exception() { - return this.m_exc; - }, - set exception(exc) { - this.m_exc = exc; - } -}; - -function stringToIcal( data ) +function stringToIcal( data, expectedErrno ) { - if (!data || data == "") { // assuming time-out + if (!data || data == "") { // assuming time-out; WTF. throw new Components.Exception( "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED ); + Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED); } - var icalRootComp = getIcsService().parseICS( data ); - checkWcapIcalErrno( icalRootComp ); + var icalRootComp; + try { + icalRootComp = getIcsService().parseICS(data); + } + catch (exc) { // map into more useful error string: + throw new Components.Exception("error parsing ical data!", + Components.interfaces.calIErrors.ICS_PARSE); + } + checkWcapIcalErrno(icalRootComp, expectedErrno); return icalRootComp; } -function stringToXml( data ) +function stringToXml( data, expectedErrno ) { if (!data || data == "") { // assuming time-out throw new Components.Exception( "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED ); + Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED); } - var xml = getDomParser().parseFromString( data, "text/xml" ); - checkWcapXmlErrno( xml ); + var xml = getDomParser().parseFromString(data, "text/xml"); + checkWcapXmlErrno(xml, expectedErrno); return xml; } -function UnicharReader( receiverFunc ) { - this.wrappedJSObject = this; - this.m_receiverFunc = receiverFunc; -} -UnicharReader.prototype = { - m_receiverFunc: null, - - // nsIUnicharStreamLoaderObserver: - onDetermineCharset: - function( loader, context, firstSegment, length ) - { - var charset = loader.channel.contentCharset; - if (!charset || charset == "") - charset = "UTF-8"; - return charset; - }, - - onStreamComplete: - function( loader, context, status, /* nsIUnicharInputStream */ unicharData ) - { - switch (status) { - case NS_BINDING_SUCCEEDED: { - if (LOG_LEVEL > 2) { - var channel = loader.channel; - logMessage( "issueAsyncRequest( \"" + - (channel ? channel.URI.spec : "") + "\" )", - "received stream." ); - } - var str = ""; - if (unicharData) { - var str_ = {}; - while (unicharData.readString( -1, str_ )) { - str += str_.value; - } - } - if (LOG_LEVEL > 1) { - var channel = loader.channel; - logMessage( "issueAsyncRequest( \"" + - (channel ? channel.URI.spec : "") + "\" )", - "contentCharset = " + loader.channel.contentCharset+ - "\nrequest result:\n" + str ); - } - this.m_receiverFunc( str ); - break; - } - case NS_BINDING_REDIRECTED: - case NS_BINDING_RETARGETED: - // just status - break; - default: // errors: - if (LOG_LEVEL > 0) { - var channel = loader.channel; - logMessage("issueAsyncRequest( \"" + - (channel ? channel.URI.spec : "") + "\" )", - "error: " + errorToString(status)); - } - this.m_receiverFunc(""); // will lead to timeout - break; - } - } -}; - -function issueAsyncRequest( url, receiverFunc ) -{ - var reader = null; - if (receiverFunc != null) { - reader = new UnicharReader( receiverFunc ); - } - var loader = - Components.classes["@mozilla.org/network/unichar-stream-loader;1"] - .createInstance(Components.interfaces.nsIUnicharStreamLoader); - logMessage( "issueAsyncRequest( \"" + url + "\" )", "opening channel." ); - var channel = getIoService().newChannel( - url, "" /* charset */, null /* baseURI */ ); - channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - loader.init( channel, reader, null /* context */, 0 /* segment size */ ); -} - -function streamToString( inStream, charset ) -{ - if (LOG_LEVEL > 2) { - logMessage( "streamToString()", - "inStream.available() = " + inStream.available() + - ", charset = " + charset ); - } - // byte-array to string: - var convStream = - Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - try { - convStream.init( inStream, charset, 0, 63 /* '?' */ ); - var str = ""; - var str_ = {}; - while (convStream.readString( -1, str_ )) { - str += str_.value; - } - } - catch (exc) { - throw new Components.Exception( - "error converting stream: " + errorToString(exc) + - "\ncharset: " + charset + - "\npartially read string: " + str, exc ); - } - return str; -} - -function issueSyncRequest( url, receiverFunc, bLogging ) -{ - if (bLogging == undefined) - bLogging = true; - if (bLogging && LOG_LEVEL > 0) { - logMessage( "issueSyncRequest( \"" + url + "\" )", - "opening channel." ); - } - - var channel = getIoService().newChannel( - url, "" /* charset */, null /* baseURI */ ); - channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - - var stream = channel.open(); - var status = channel.status; - if (status == Components.results.NS_OK) { - var charset = channel.contentCharset; - if (!charset || charset == "") - charset = "UTF-8"; - var str = streamToString( stream, charset ); - if (bLogging && LOG_LEVEL > 1) { - logMessage( "issueSyncRequest( \"" + url + "\" )", - "returned: " + str ); - } - if (receiverFunc) { - receiverFunc( str ); - } - return str; - } - else if (bLogging && LOG_LEVEL > 0) { - logMessage( "issueSyncRequest( \"" + url + "\" )", - "failed: " + errorToString(status) ); - } - - throw status; -} - -function issueSyncXMLRequest( url, receiverFunc, bLogging ) -{ - var str = issueSyncRequest( url, null, bLogging ); - var xml = getDomParser().parseFromString( str, "text/xml" ); - if (receiverFunc) { - receiverFunc( xml ); - } - return xml; -} - -// response object for Calendar.issueRequest() -function RequestQueue() { - this.m_requests = []; -} -RequestQueue.prototype = { - m_requests: null, - m_token: 0, - - postRequest: - function( func ) - { - var token = this.m_token; - this.m_token += 1; - this.m_requests.push( { m_token: token, m_func: func } ); - var len = this.m_requests.length; - logMessage( "RequestQueue::postRequest()", - "queueing request. token=" + token + - ", open requests=" + len ); - if (len == 1) { - func( token ); - } - }, - - requestCompleted: - function( requestToken ) - { - var len_ = this.m_requests.length; - this.m_requests = this.m_requests.filter( - function(x) { return x.m_token != requestToken; } ); - var len = this.m_requests.length; - logMessage( "RequestQueue::requestCompleted()", - "token=" + requestToken + - ((len > 0 && len_ == len) ? "(expired !!!)" : "") + - ", open requests=" + len ); - if (len > 0) { - var entry = this.m_requests[0]; - entry.m_func( entry.m_token ); - } - } -}; - diff --git a/calendar/providers/wcap/calWcapSession.js b/calendar/providers/wcap/calWcapSession.js index 9bf235b8c95..ed9d61e2a15 100644 --- a/calendar/providers/wcap/calWcapSession.js +++ b/calendar/providers/wcap/calWcapSession.js @@ -37,49 +37,27 @@ * * ***** END LICENSE BLOCK ***** */ -// globals: -var g_serverTimeDiffs = {}; -var g_allSupportedTimezones = {}; - function calWcapSession() { this.wrappedJSObject = this; this.m_observers = []; - this.m_calIdToCalendar = {}; - this.m_asyncQueue = new AsyncQueue(); - this.m_calPropsTimer = new Timer(); - this.m_reconnectTimer = new Timer(); - - // init queued calls: - this.getFreeBusyTimes = makeQueuedCall( - this.asyncQueue, this, this.getFreeBusyTimes_queued); - this.searchForCalendars = makeQueuedCall( - this.asyncQueue, this, this.searchForCalendars_queued); + this.m_loginQueue = []; + this.m_subscribedCals = {}; } - calWcapSession.prototype = { - m_asyncQueue: null, - get asyncQueue() { return this.m_asyncQueue; }, - - m_ifaces: [ Components.interfaces.calIWcapSession, + m_ifaces: [ calIWcapSession, Components.interfaces.calICalendarManagerObserver, Components.interfaces.nsIInterfaceRequestor, Components.interfaces.nsIClassInfo, Components.interfaces.nsISupports ], // nsISupports: - QueryInterface: - function( iid ) - { - for each ( var iface in this.m_ifaces ) { - if (iid.equals( iface )) - return this; - } - throw Components.results.NS_ERROR_NO_INTERFACE; + QueryInterface: function calWcapSession_QueryInterface(iid) { + qiface(this.m_ifaces, iid); + return this; }, // nsIClassInfo: - getInterfaces: - function( count ) + getInterfaces: function calWcapSession_getInterfaces(count) { count.value = this.m_ifaces.length; return this.m_ifaces; @@ -93,15 +71,14 @@ calWcapSession.prototype = { get classID() { return calWcapCalendarModule.WcapSessionInfo.classID; }, - getHelperForLanguage: function( language ) { return null; }, + getHelperForLanguage: + function calWcapSession_getHelperForLanguage(language) { return null; }, implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT, flags: 0, // nsIInterfaceRequestor: - getInterface: - function( iid, instance ) - { + getInterface: function calWcapSession_getInterface(iid, instance) { if (iid.equals(Components.interfaces.nsIAuthPrompt)) { // use the window watcher service to get a nsIAuthPrompt impl return getWindowWatcher().getNewAuthPrompter(null); @@ -114,588 +91,680 @@ calWcapSession.prototype = { return null; }, - toString: - function( msg ) + toString: function calWcapSession_toString(msg) { var str = (this.uri ? this.uri.spec : "no uri"); - if (this.m_userId != null) { - str += (", userId=" + this.userId); + if (this.credentials.userId) { + str += (", userId=" + this.credentials.userId); } - if (this.m_sessionId == null) { + if (!this.m_sessionId) { str += (getIoService().offline ? ", offline" : ", not logged in"); } return str; }, - log: - function( msg, context ) - { - return logMessage( context ? context : this.toString(), msg ); - }, - logError: - function( err, context ) - { - var msg = errorToString(err); - Components.utils.reportError( this.log("error: " + msg, context) ); - return msg; - }, - notifyError: - function( err ) + notifyError: function calWcapSession_notifyError(err) { debugger; - var msg = this.logError(err); + var msg = logError(err, this); this.notifyObservers( "onError", err instanceof Components.interfaces.nsIException - ? [err.result, err.message] : [-1, msg] ); + ? [err.result, err.message] : [isNaN(err) ? -1 : err, msg] ); }, m_observers: null, - notifyObservers: - function( func, args ) + notifyObservers: function calWcapSession_notifyObservers(func, args) { - this.m_observers.forEach( - function( obj ) { - try { - obj[func].apply( obj, args ); - } - catch (exc) { - // don't call notifyError() here: - Components.utils.reportError( exc ); - } - } ); - }, - - addObserver: - function( observer ) - { - if (this.m_observers.indexOf( observer ) == -1) { - this.m_observers.push( observer ); + if (!g_bShutdown) { + this.m_observers.forEach( + function notifyFunc(obj) { + try { + obj[func].apply(obj, args); + } + catch (exc) { + // don't call notifyError() here: + Components.utils.reportError(exc); + } + }); } }, - removeObserver: - function( observer ) + addObserver: function calWcapSession_addObserver(observer) + { + if (this.m_observers.indexOf(observer) == -1) + this.m_observers.push(observer); + }, + + removeObserver: function calWcapSession_removeObserver(observer) { this.m_observers = this.m_observers.filter( - function(x) { return x != observer; } ); + function filterFunc(x) { return x != observer; } ); }, - getSupportedTimezones: - function( bRefresh ) + m_serverTimezones: null, + isSupportedTimezone: function calWcapSession_isSupportedTimezone(tzid) { - var url = this.getCommandUrl("get_all_timezones"); // + logged in - var key = this.sessionUri.hostPort; - if (bRefresh || !g_allSupportedTimezones[key]) { - url += "&fmt-out=text%2Fcalendar"; - var icalRootComp = this.issueSyncRequest( url, stringToIcal ); - var tzids = []; - var this_ = this; - forEachIcalComponent( - icalRootComp, "VTIMEZONE", - function( subComp ) { - try { - var tzCal = getIcsService().createIcalComponent( - "VCALENDAR" ); - subComp = subComp.clone(); - tzCal.addSubcomponent( subComp ); - getIcsService().addTimezone( tzCal, "", "" ); - tzids.push( subComp.getFirstProperty("TZID").value ); - } - catch (exc) { // ignore errors: - this_.logError( exc ); - } - } ); - g_allSupportedTimezones[key] = tzids; + if (!this.m_serverTimezones) { + throw new Components.Exception( + "early run into getSupportedTimezones()!", + Components.results.NS_ERROR_NOT_AVAILABLE); } - return g_allSupportedTimezones[key]; + return this.m_serverTimezones.some( + function someFunc(id) { return tzid == id; } ); }, - getServerTime: - function( localTime ) + m_serverTimeDiff: null, + getServerTime: function calWcapSession_getServerTime(localTime) { + if (this.m_serverTimeDiff === null) { + throw new Components.Exception( + "early run into getServerTime()!", + Components.results.NS_ERROR_NOT_AVAILABLE); + } var ret = (localTime ? localTime.clone() : getTime()); - ret.addDuration( this.getServerTimeDiff() ); + ret.addDuration(this.m_serverTimeDiff); return ret; }, - getServerTimeDiff: - function( bRefresh ) - { - var url = this.getCommandUrl("gettime"); // + logged in - var key = this.sessionUri.hostPort; - if (bRefresh || !g_serverTimeDiffs[key]) { - url += "&fmt-out=text%2Fcalendar"; - // xxx todo: think about - // assure that locally calculated server time is smaller than the - // current (real) server time: - var icalRootComp = this.issueSyncRequest( url, stringToIcal ); - var localTime = getTime(); - var serverTime = getDatetimeFromIcalProp( - icalRootComp.getFirstProperty( "X-NSCP-WCAPTIME" ) ); - var diff = serverTime.subtractDate( localTime ); - this.log( "server time diff is: " + diff ); - g_serverTimeDiffs[key] = diff; - } - return g_serverTimeDiffs[key]; - }, - - isSupportedTimezone: - function( tzid ) - { - var tzids = this.getSupportedTimezones(); - for each ( var id in tzids ) { - if (id == tzid) - return true; - } - return false; - }, - m_sessionId: null, - m_bNoLoginsAnymore: false, - m_calPropsTimer: null, - m_reconnectTimer: null, + m_loginQueue: null, + m_loginLock: false, + m_subscribedCals: null, getSessionId: - function( timedOutSessionId ) + function calWcapSession_getSessionId(request, respFunc, timedOutSessionId) { if (getIoService().offline) { - this.log( "in offline mode." ); - throw new Components.Exception( - "The requested action could not be completed while the " + - "networking library is in the offline state.", - NS_ERROR_OFFLINE ); - } - if (this.m_bNoLoginsAnymore) { - this.log( "login has failed, no logins anymore for this user." ); - throw new Components.Exception( - "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED ); + log("in offline mode.", this); + respFunc(new Components.Exception( + "The requested action could not be completed while the " + + "networking library is in the offline state.", + NS_ERROR_OFFLINE)); + return; } - if (!this.m_sessionId || this.m_sessionId == timedOutSessionId) { + log("login queue lock: " + this.m_loginLock + + ", length: " + this.m_loginQueue.length, this); + + if (this.m_loginLock) { + this.m_loginQueue.push(respFunc); + log("login queue: " + this.m_loginQueue.length); + } + else { + if (this.m_sessionId && this.m_sessionId != timedOutSessionId) { + respFunc(null, this.m_sessionId); + return; + } - try { - this.m_sessionId = null; - if (timedOutSessionId) { - this.log( "prompting to reconnect.", "session timeout" ); - var prompt = getWindowWatcher().getNewPrompter(null); - var bundle = getWcapBundle(); - if (!prompt.confirm( - bundle.GetStringFromName( - "reconnectConfirmation.label"), - bundle.formatStringFromName( - "reconnectConfirmation.text", - [this.uri.hostPort], 1 ) )) { - this.log( "reconnect cancelled." ); - throw new Components.Exception( - "Login failed. Invalid session ID.", - Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED ); + this.m_loginLock = true; + log("locked login queue.", this); + this.m_sessionId = null; // invalidate for relogin + this.assureInstalledLogoutObservers(); + + if (timedOutSessionId) + log("reconnecting due to session timeout...", this); + + var this_ = this; + this.getSessionId_( + request, + function getSessionId_resp(err, sessionId) { + log("getSessionId_resp(): " + sessionId, this_); + if (err) { + if (getResultCode(err) == calIWcapErrors.WCAP_LOGIN_FAILED) { + // notify login failures once here only: + this_.notifyError(err); + } + } + else { + this_.m_sessionId = sessionId; + } + + var queue = this_.m_loginQueue; + this_.m_loginLock = false; + this_.m_loginQueue = []; + log("unlocked login queue.", this_); + + // first request: + respFunc(err, sessionId); + // dequeue remaining: + while (queue.length > 0) { + queue.shift()(err, sessionId); + } + }); + } + }, + + getSessionId_: function calWcapSession_getSessionId_(request, respFunc) + { + var this_ = this; + this.getLoginText( + request, + // probe whether server is accessible and responds login text: + function getLoginText_resp(err, loginText) { + if (err) { + respFunc(err); + return; + } + // lookup password manager, then try login or prompt/login: + log("attempting to get a session id for " + this_.sessionUri.spec, this_); + + if (!this_.sessionUri.schemeIs("https") && + !confirmInsecureLogin(this_.sessionUri)) { + log("user rejected insecure login on " + this_.sessionUri.spec, this_); + respFunc(new Components.Exception( + "Login failed. Invalid session ID.", + calIWcapErrors.WCAP_LOGIN_FAILED)); + return; + } + + var outUser = { value: this_.credentials.userId }; + var outPW = { value: this_.credentials.pw }; + var outSavePW = { value: false }; + + // pw mgr host names must not have a trailing slash + var passwordManager = + Components.classes["@mozilla.org/passwordmanager;1"] + .getService(Components.interfaces.nsIPasswordManager); + var pwHost = this_.sessionUri.spec; + if (pwHost[pwHost.length - 1] == '/') + pwHost = pwHost.substr(0, pwHost.length - 1); + + if (!outPW.value) { // lookup pw manager + log("looking in pw db for: " + pwHost, this_); + try { + var enumerator = passwordManager.enumerator; + while (enumerator.hasMoreElements()) { + var pwEntry = enumerator.getNext().QueryInterface( + Components.interfaces.nsIPassword); + if (LOG_LEVEL > 1) { + log("pw entry:\n\thost=" + pwEntry.host + + "\n\tuser=" + pwEntry.user, this_); + } + if (pwEntry.host == pwHost) { + // found an entry matching URI: + outUser.value = pwEntry.user; + outPW.value = pwEntry.password; + log("password entry found for host " + pwHost + + "\nuser is " + outUser.value, this_); + break; + } + } + } + catch (exc) { // just log error + logError("[password manager lookup] " + errorToString(exc), this_); } } - var sessionUri = this.uri.clone(); - sessionUri.userPass = ""; - if (!sessionUri.schemeIs("https") && - sessionUri.port == -1 /* no port specified */) - { - // silently probe for https support: - try { // enforce https: - sessionUri.scheme = "https"; - this.getSessionId_(sessionUri); + function promptAndLoginLoop_resp(err, sessionId) { + if (getResultCode(err) == calIWcapErrors.WCAP_LOGIN_FAILED) { + log("prompting for user/pw...", this_); + var prompt = getWindowWatcher().getNewPrompter(null); + if (prompt.promptUsernameAndPassword( + getWcapBundle().GetStringFromName("loginDialog.label"), + loginText, outUser, outPW, + getWcapBundle().GetStringFromName("loginDialog.check.text"), + outSavePW)) { + this_.login(request, promptAndLoginLoop_resp, + outUser.value, outPW.value); + } + else { + log("login prompt cancelled.", this_); + respFunc(new Components.Exception( + "Login failed. Invalid session ID.", + calIWcapErrors.WCAP_LOGIN_FAILED)); + } } - catch (exc) { - // restore scheme: - sessionUri.scheme = this.uri.scheme; - if (testResultCode(exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED)) - throw exc; // forward login failures - // but ignore connection errors - } - } - if (!this.m_sessionId) - this.getSessionId_(sessionUri); - } - catch (exc) { - this.m_bNoLoginsAnymore = true; - this.logError( exc ); - this.log( "no logins anymore." ); - if (!testResultCode(exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED) && - this.sessionUri) - { - // reconnect failure, sessionUri is known: - this.log("probing " + this.sessionUri.spec + - " in 2 minutes..."); - var this_ = this; - this.m_reconnectTimer.initWithCallback( - { // nsITimerCallback: - notify: function(timer_) { + else if (err) + respFunc(err); + else { + if (outSavePW.value) { + // so try to remove old pw from db first: try { - this_.log("probing " + - this_.sessionUri.spec + - "..."); - issueAsyncRequest( - this_.sessionUri.spec + - "version.wcap?fmt-out=text%2Fcalendar", - function(str) { - if (str.indexOf("BEGIN:VCALENDAR") >= 0) - { - this_.log(this_.sessionUri.spec + - " ok again."); - timer_.cancel(); - this_.m_bNoLoginsAnymore = false; - } - }); + passwordManager.removeUser( + pwHost, outUser.value); + log("removed from pw db: " + pwHost, this_); } - catch (exc) { // again in 2 minutes + catch (exc) { + } + try { // save pw under session uri: + passwordManager.addUser(pwHost, outUser.value, outPW.value); + log("added to pw db: " + pwHost, this_); + } + catch (exc) { + logError("[adding pw to db] " + errorToString(exc), this_); } } - }, - 120 * 1000, - Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); - } - throw exc; - } - - this.assureInstalledLogoutObservers(); - - this.getServerTimeDiff(true /* refresh */); - - if (!timedOutSessionId) // timezones don't change that frequently - this.getSupportedTimezones(true /* refresh */); - - // reset calprops of all subscribed calendars before: - this.getSubscribedCalendars({}).forEach( - function(cal) { cal.resetCalProps(); } ); - - this.m_calPropsTimer.initWithCallback( - { // nsITimerCallback: - m_pos: 0, - m_session: this, - notify: function(timer_) { - var cals = this.m_session.getSubscribedCalendars({}); - var c = (cals.length - this.m_pos); - if (c > 0) { - if (c > 5) - c = 5; - while (c--) - cals[this.m_pos++].getCalProps_(true /*async*/); - } - if (this.m_pos >= cals.length) - timer_.cancel(); - } - }, - 15 * 1000, - Components.interfaces.nsITimer.TYPE_REPEATING_SLACK ); - } - - if (!this.m_sessionId) { - throw new Components.Exception( - "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED ); - } - return this.m_sessionId; - }, - - assureSecureLogin: - function( sessionUri ) - { - if (!sessionUri.schemeIs("https") && - !confirmInsecureLogin(sessionUri)) { - this.log( "user rejected insecure login on " + sessionUri.spec ); - throw new Components.Exception( - "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED ); - } - }, - - m_loginUser: null, - m_loginPW: null, - - getSessionId_: - function( sessionUri ) - { - // probe whether server is accessible: - var loginText = this.getServerInfo(sessionUri); - - this.log( "attempting to get a session id for " + sessionUri.spec ); - var outUser = { value: this.userId }; - var outPW = { value: null }; - if (this.m_loginUser == outUser.value) - outPW.value = this.m_loginPW; - - var passwordManager = - Components.classes["@mozilla.org/passwordmanager;1"] - .getService(Components.interfaces.nsIPasswordManager); - - // xxx todo: pw host names must not have a trailing slash - var pwHost = sessionUri.spec; - if (pwHost[pwHost.length - 1] == '/') - pwHost = pwHost.substr(0, pwHost.length - 1); - if (!outPW.value) { - this.log( "looking in pw db for: " + pwHost ); - try { - var enumerator = passwordManager.enumerator; - while (enumerator.hasMoreElements()) { - var pwEntry = enumerator.getNext().QueryInterface( - Components.interfaces.nsIPassword ); - if (LOG_LEVEL > 1) { - this.log( "pw entry:\n\thost=" + pwEntry.host + - "\n\tuser=" + pwEntry.user ); - } - if (pwEntry.host == pwHost) { - // found an entry matching URI: - outUser.value = pwEntry.user; - outPW.value = pwEntry.password; - break; + this_.credentials.userId = outUser.value; + this_.credentials.pw = outPW.value; + this_.setupSession( + sessionId, + request, + function setupSession_resp(err) { + respFunc(err, sessionId); + }); } } - } - catch (exc) { // just log error - this.logError(exc, "password manager lookup"); - } - - if (outPW.value) { - this.log( "password entry found for host " + pwHost + - "\nuser is " + outUser.value ); - } - else - this.log( "no password entry found for " + pwHost ); - } - - if (outPW.value) { - this.assureSecureLogin(sessionUri); - this.login_( sessionUri, outUser.value, outPW.value ); - } - - if (!this.m_sessionId) { - if (outPW.value) { - if (outPW.value != this.m_loginPW) { // pw came from manager: - // login failed before, so try to remove from pw db: - try { - passwordManager.removeUser( pwHost, outUser.value ); - this.log( "removed from pw db: " + pwHost ); - } - catch (exc) { - this.logError( "error removing from pw db: " + exc ); - } - } - } - else // if not already checked in pw manager run - this.assureSecureLogin(sessionUri); - - var savePW = { value: false }; - while (!this.m_sessionId) { - this.log( "prompting for user/pw..." ); - var prompt = getWindowWatcher().getNewPrompter(null); - if (prompt.promptUsernameAndPassword( - getWcapBundle().GetStringFromName( - "loginDialog.label"), - loginText, outUser, outPW, - getWcapBundle().GetStringFromName( - "loginDialog.check.text"), - savePW )) - { - if (this.login_( sessionUri, outUser.value, outPW.value )) - break; + + if (outPW.value) { + this_.login(request, promptAndLoginLoop_resp, + outUser.value, outPW.value); } else { - this.log( "login prompt cancelled." ); - throw new Components.Exception( - "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors.WCAP_LOGIN_FAILED); + promptAndLoginLoop_resp(calIWcapErrors.WCAP_LOGIN_FAILED); } - } - // for next login in same process run: - this.m_loginUser = outUser.value; - this.m_loginPW = outPW.value; - if (savePW.value) { - try { // save pw under session uri: - passwordManager.addUser( pwHost, - outUser.value, outPW.value ); - this.log( "added to pw db: " + pwHost ); + }); + }, + + login: function calWcapSession_login(request, respFunc, user, pw) + { + var this_ = this; + issueNetworkRequest( + request, + function netResp(err, str) { + var sessionId; + try { + if (err) + throw err; + // currently, xml parsing at an early stage during + // process startup does not work reliably, so use + // libical parsing for now: + var icalRootComp = stringToIcal(str); + var prop = icalRootComp.getFirstProperty("X-NSCP-WCAP-SESSION-ID"); + if (!prop) { + throw new Components.Exception( + "missing X-NSCP-WCAP-SESSION-ID in\n" + str); + } + sessionId = prop.value; + log("login succeeded: " + sessionId, this_); } catch (exc) { - this.logError( "error adding pw to db: " + exc ); + err = exc; + var rc = getResultCode(exc); + if (rc == calIWcapErrors.WCAP_LOGIN_FAILED) { + logError(exc, this_); // log login failure + } + else if (getErrorModule(rc) == NS_ERROR_MODULE_NETWORK) { + // server seems unavailable: + err = new Components.Exception( + getWcapBundle().formatStringFromName( + "accessingServerFailedError.text", + [this_.sessionUri.hostPort], 1), + exc); + } + } + respFunc(err, sessionId); + }, + this_.sessionUri.spec + "login.wcap?fmt-out=text%2Fcalendar&user=" + + encodeURIComponent(user) + "&password=" + encodeURIComponent(pw), + false /* no logging */); + }, + + logout: function calWcapSession_logout(listener) + { + this.m_aboutToLogout = true; + + // unregister all subscribed cals first, don't modify subscriptions upon + // unregisterCalendar: + var subscribedCals = this.m_subscribedCals; + this.m_subscribedCals = {}; + if (!g_bShutdown) { + for each (var cal in subscribedCals) { + try { + getCalendarManager().unregisterCalendar(cal); + } + catch (exc) { + this.notifyError(exc); } } } - return this.m_sessionId; - }, - - login_: - function( sessionUri, user, pw ) - { - var str; - var icalRootComp = null; - try { - // currently, xml parsing at an early stage during process startup - // does not work reliably, so use libical parsing for now: - str = issueSyncRequest( - sessionUri.spec + "login.wcap?fmt-out=text%2Fcalendar&user=" + - encodeURIComponent(user) + - "&password=" + encodeURIComponent(pw), - null /* receiverFunc */, false /* no logging */ ); - if (str.indexOf("BEGIN:VCALENDAR") < 0) - throw new Components.Exception("no ical data returned!"); - icalRootComp = getIcsService().parseICS( str ); - checkWcapIcalErrno( icalRootComp ); - } - catch (exc) { - if (testResultCode(exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED)) { - this.logError( exc ); // log wrong pw - return false; - } - if (!isNaN(exc) && getErrorModule(exc) == NS_ERROR_MODULE_NETWORK) { - // server seems unavailable: - throw new Components.Exception( - getWcapBundle().formatStringFromName( - "accessingServerFailedError.text", - [sessionUri.hostPort], 1 ), - exc ); - } - throw exc; - } - var prop = icalRootComp.getFirstProperty("X-NSCP-WCAP-SESSION-ID"); - if (!prop) { - throw new Components.Exception( - "missing X-NSCP-WCAP-SESSION-ID in\n" + str ); - } - this.m_sessionId = prop.value; - this.m_userId = user; - this.m_sessionUri = sessionUri; - this.log( "login succeeded, setting sessionUri to " + - this.sessionUri.spec ); - return true; - }, - - getServerInfo: - function( uri ) - { - var str; - var icalRootComp = null; - try { - // currently, xml parsing at an early stage during process startup - // does not work reliably, so use libical: - str = issueSyncRequest( - uri.spec + "version.wcap?fmt-out=text%2Fcalendar" ); - if (str.indexOf("BEGIN:VCALENDAR") < 0) - throw new Components.Exception("no ical data returned!"); - icalRootComp = getIcsService().parseICS( str ); - } - catch (exc) { - this.log( errorToString(exc) ); // soft error; request denied etc. - throw new Components.Exception( - getWcapBundle().formatStringFromName( - "accessingServerFailedError.text", [uri.hostPort], 1 ), - isNaN(exc) ? Components.results.NS_ERROR_FAILURE : exc ); - } - - var prop = icalRootComp.getFirstProperty( "X-NSCP-WCAPVERSION" ); - if (!prop) { - throw new Components.Exception( - "missing X-NSCP-WCAPVERSION in\n" + str ); - } - var wcapVersion = parseInt(prop.value); - if (wcapVersion < 3) { - var strVers = prop.value; - var vars = [uri.hostPort]; - prop = icalRootComp.getFirstProperty( "PRODID" ); - vars.push( prop ? prop.value : "" ); - prop = icalRootComp.getFirstProperty( "X-NSCP-SERVERVERSION" ); - vars.push( prop ? prop.value : "" ); - vars.push( strVers ); - - var prompt = getWindowWatcher().getNewPrompter(null); - var bundle = getWcapBundle(); - var labelText = bundle.GetStringFromName( - "insufficientWcapVersionConfirmation.label"); - if (!prompt.confirm( labelText, - bundle.formatStringFromName( - "insufficientWcapVersionConfirmation.text", - vars, vars.length ) )) { - throw new Components.Exception(labelText); - } - } - return getWcapBundle().formatStringFromName( "loginDialog.text", - [uri.hostPort], 1 ); - }, - - getCommandUrl: - function( wcapCommand ) - { - if (!this.uri) - throw new Components.Exception("no URI!"); - // ensure established session, so sessionUri and userId are set; - // (calId defaults to userId) if not set: - this.getSessionId(); - return (this.sessionUri.spec + - wcapCommand + ".wcap?appid=mozilla-calendar"); - }, - - issueRequest: - function( url, issueFunc, dataConvFunc, receiverFunc ) - { - var sessionId = this.getSessionId(); var this_ = this; - issueFunc( - url + ("&id=" + encodeURIComponent(sessionId)), - function( data ) { - var wcapResponse = new WcapResponse(); + var request = new calWcapRequest( + function logout_resp(request, err) { + this_.m_aboutToLogout = false; + if (err) + logError(err, this_); + else + log("logout succeeded.", this_); + if (listener) + listener.onRequestResult(request, err); + }, + log("logout", this)); + + var url = null; + if (this.m_sessionId) { + log("attempting to log out...", this); + // although io service's offline flag is already + // set BEFORE notification + // (about to go offline, nsIOService.cpp). + // WTF. + url = (this.sessionUri.spec + "logout.wcap?fmt-out=text%2Fxml&id=" + + encodeURIComponent(this.m_sessionId)); + this.m_sessionId = null; + } + this.m_credentials = null; + + if (url) { + issueNetworkRequest( + request, + function netResp(err, str) { + if (err) + throw err; + stringToXml(str, -1 /* logout successfull */); + }, url); + } + else { + request.execRespFunc(); + } + return request; + }, + + getLoginText: function calWcapSession_getLoginText(request, respFunc) + { + // currently, xml parsing at an early stage during process startup + // does not work reliably, so use libical: + var this_ = this; + issueNetworkRequest( + request, + function netResp(err, str) { + var loginText; try { - try { - wcapResponse.data = dataConvFunc( data ); - } - catch (exc) { - if (testResultCode( - exc, Components.interfaces. - calIWcapErrors.WCAP_LOGIN_FAILED)) /* timeout */ - { - // getting a new session will throw any exception in - // this block, thus it is notified into receiverFunc - this_.getSessionId( - sessionId /* (old) timed-out session */ ); - // try again: - this_.issueRequest( - url, issueFunc, dataConvFunc, receiverFunc ); - return; + var icalRootComp; + if (!err) { + try { + icalRootComp = stringToIcal(str); + } + catch (exc) { + err = exc; } - throw exc; // rethrow } + if (err) { // soft error; request denied etc. + // map into localized message: + throw new Components.Exception( + getWcapBundle().formatStringFromName( + "accessingServerFailedError.text", + [this_.sessionUri.hostPort], 1), + calIWcapErrors.WCAP_LOGIN_FAILED); + } + var prop = icalRootComp.getFirstProperty("X-NSCP-WCAPVERSION"); + if (!prop) + throw new Components.Exception("missing X-NSCP-WCAPVERSION!"); + var wcapVersion = parseInt(prop.value); + if (wcapVersion < 3) { + var strVers = prop.value; + var vars = [this_.sessionUri.hostPort]; + prop = icalRootComp.getFirstProperty("PRODID"); + vars.push(prop ? prop.value : ""); + prop = icalRootComp.getFirstProperty("X-NSCP-SERVERVERSION"); + vars.push(prop ? prop.value : ""); + vars.push(strVers); + + var prompt = getWindowWatcher().getNewPrompter(null); + var bundle = getWcapBundle(); + var labelText = bundle.GetStringFromName( + "insufficientWcapVersionConfirmation.label"); + if (!prompt.confirm( + labelText, + bundle.formatStringFromName( + "insufficientWcapVersionConfirmation.text", + vars, vars.length))) { + throw new Components.Exception( + labelText, calIWcapErrors.WCAP_LOGIN_FAILED); + } + } + loginText = getWcapBundle().formatStringFromName( + "loginDialog.text", [this_.sessionUri.hostPort], 1); } catch (exc) { - // setting the request's exception will rethrow exception - // when request's data is retrieved. - wcapResponse.exception = exc; + err = exc; } - receiverFunc( wcapResponse ); - } ); + respFunc(err, loginText); + }, + this_.sessionUri.spec + "version.wcap?fmt-out=text%2Fcalendar"); }, - issueAsyncRequest: - function( url, dataConvFunc, receiverFunc ) + setupSession: + function calWcapSession_setupSession(sessionId, request_, respFunc) { - this.issueRequest( - url, issueAsyncRequest, dataConvFunc, receiverFunc ); + var this_ = this; + var request = new calWcapRequest( + function setupSession_resp(request_, err) { + log("setupSession_resp finished: " + errorToString(err), this_); + respFunc(err); + }, + log("setupSession", this)); + request_.attachSubRequest(request); + + request.lockPending(); + try { + var this_ = this; + this.issueNetworkRequest_( + request, + function userprefs_resp(err, data) { + if (err) + throw err; + this_.credentials.userPrefs = data; + log("installed user prefs.", this_); + + this_.issueNetworkRequest_( + request, + function calprops_resp(err, data) { + if (err) + throw err; + this_.defaultCalendar.m_calProps = data; + log("installed default cal props.", this_); + }, + stringToXml, "get_calprops", + "&fmt-out=text%2Fxml&calid=" + + encodeURIComponent(this_.defaultCalId), + sessionId); + if (getPref("calendar.wcap.subscriptions", false)) + this_.installSubscribedCals(sessionId, request); + }, + stringToXml, "get_userprefs", + "&fmt-out=text%2Fxml&userid=" + encodeURIComponent(this.credentials.userId), + sessionId); + this.installServerTimeDiff(sessionId, request); + this.installServerTimezones(sessionId, request); + } + finally { + request.unlockPending(); + } }, - issueSyncRequest: - function( url, dataConvFunc, receiverFunc ) + installServerTimeDiff: + function calWcapSession_installServerTimeDiff(sessionId, request) { - var ret = null; - this.issueRequest( - url, issueSyncRequest, - dataConvFunc, - function( wcapResponse ) { - if (receiverFunc) { - receiverFunc( wcapResponse ); + var this_ = this; + this.issueNetworkRequest_( + request, + function netResp(err, data) { + if (err) + throw err; + // xxx todo: think about + // assure that locally calculated server time is smaller + // than the current (real) server time: + var localTime = getTime(); + var serverTime = getDatetimeFromIcalProp( + data.getFirstProperty("X-NSCP-WCAPTIME")); + this_.m_serverTimeDiff = serverTime.subtractDate(localTime); + log("server time diff is: " + this_.m_serverTimeDiff, this_); + }, + stringToIcal, "gettime", "&fmt-out=text%2Fcalendar", + sessionId); + }, + + installServerTimezones: + function calWcapSession_installServerTimezones(sessionId, request) + { + this.m_serverTimezones = []; + var this_ = this; + this_.issueNetworkRequest_( + request, + function netResp(err, data) { + if (err) + throw err; + var tzids = []; + var icsService = getIcsService(); + forEachIcalComponent( + data, "VTIMEZONE", + function eachComp(subComp) { + try { + var tzCal = icsService.createIcalComponent("VCALENDAR"); + subComp = subComp.clone(); + tzCal.addSubcomponent(subComp); + icsService.addTimezone(tzCal, "", ""); + this_.m_serverTimezones.push( + subComp.getFirstProperty("TZID").value); + } + catch (exc) { // ignore but errors: + logError(exc, this_); + } + }); + log("installed timezones.", this_); + }, + stringToIcal, "get_all_timezones", "&fmt-out=text%2Fcalendar", + sessionId); + }, + + installSubscribedCals: + function calWcapSession_installSubscribedCals(sessionId, request) + { + var this_ = this; + // user may have dangling users referred in his subscription list, so + // retrieve each by each, don't break: + var list = this.getUserPreferences("X-NSCP-WCAP-PREF-icsSubscribed"); + for each( var item in list ) { + var ar = item.split(','); + // ',', '$' are not encoded. ',' can be handled here. WTF. + for each ( var a in ar ) { + var dollar = a.indexOf('$'); + if (dollar >= 0) { + var calId = a.substring(0, dollar); + if (calId == this.defaultCalId) + continue; + try { + var key = encodeURIComponent(calId); + var cal = this_.m_subscribedCals[key]; + if (!cal) { + this.issueNetworkRequest_( + request, + function calprops_resp(err, xml) { + try { + if (err) + throw err; + var cal = createWcapCalendar(this_, xml); + this_.m_subscribedCals[encodeURIComponent(cal.calId)] = cal; + getCalendarManager().registerCalendar(cal); + log("installed subscribed calendar: " + cal.calId, this_); + } + catch (exc) { + // ignore but log any errors on subscribed calendars: + logError(exc, this_); + } + }, + stringToXml, "get_calprops", + "&fmt-out=text%2Fxml&calid=" + key, + sessionId); +// var listener = { +// onRequestResult: function onRequestResult(request, result) { +// try { +// if (!request.succeeded) +// throw request.status; +// if (result.length < 1) +// throw Components.results.NS_ERROR_UNEXPECTED; +// var cal = result[0]; +// getCalendarManager().registerCalendar(cal); +// log("installed subscribed calendar: " + cal.calId, +// this_); +// } +// catch (exc) { +// logError(exc, this_); +// } +// } +// }; +// this.searchForCalendars( +// calId, +// calIWcapSession.SEARCH_STRING_EXACT | +// calIWcapSession.SEARCH_INCLUDE_CALID, +// listener); + } + } + catch (exc) { // ignore but log any errors on subscribed calendars: + logError(exc, this); + } } - ret = wcapResponse.data; // may throw - } ); - return ret; + } + } + }, + + getCommandUrl: function calWcapSession_getCommandUrl(wcapCommand, params, sessionId) + { + var url = this.sessionUri.spec; + url += (wcapCommand + ".wcap?appid=mozilla-calendar"); + url += params; + url += ("&id=" + encodeURIComponent(sessionId)); + return url; + }, + + issueNetworkRequest: function calWcapSession_issueNetworkRequest( + request, respFunc, dataConvFunc, wcapCommand, params) + { + var this_ = this; + function getSessionId_resp(err, sessionId) { + if (err) + respFunc(err); + else { + // else have session uri and id: + this_.issueNetworkRequest_( + request, + function issueNetworkRequest_resp(err, data) { + // timeout? + if (getResultCode(err) == calIWcapErrors.WCAP_LOGIN_FAILED) { + // try again: + this_.getSessionId( + request, + getSessionId_resp, + sessionId/* (old) timed-out session */); + return; + } + respFunc(err, data); + }, + dataConvFunc, wcapCommand, params, sessionId); + } + } + this.getSessionId(request, getSessionId_resp); + }, + + issueNetworkRequest_: function calWcapSession_issueNetworkRequest_( + request, respFunc, dataConvFunc, wcapCommand, params, sessionId) + { + var url = this.getCommandUrl(wcapCommand, params, sessionId); + issueNetworkRequest( + request, + function netResp(err, str) { + var data; + if (!err) { + try { + data = dataConvFunc(str); + } + catch (exc) { + err = exc; + } + } + respFunc(err, data); + }, url); + }, + + m_credentials: null, + get credentials() { + if (!this.m_credentials) + this.m_credentials = { + userId: "", + pw: "", + userPrefs: null + }; + return this.m_credentials; }, // calIWcapSession: @@ -704,299 +773,55 @@ calWcapSession.prototype = { m_sessionUri: null, get uri() { return this.m_uri; }, get sessionUri() { return this.m_sessionUri; }, - set uri( thatUri ) - { - if (this.m_uri == null || thatUri == null || - !this.m_uri.equals(thatUri)) - { - this.logout(); + set uri(thatUri) { + if (!this.m_uri || !thatUri || !this.m_uri.equals(thatUri)) { + this.logout(null); this.m_uri = null; - if (thatUri != null) { - // sensible default for user id login: - var username = decodeURIComponent( thatUri.username ); - if (username != "") { - // xxx todo: might vanish soon... - // patching the default calId via url: - this.m_defaultCalId = username; - var nColon = username.indexOf(':'); - this.m_userId = (nColon >= 0 - ? username.substr(0, nColon) : username); - } + this.m_sessionUri = null; + if (thatUri) { this.m_uri = thatUri.clone(); - this.log( "setting uri to " + this.uri.spec ); + this.m_sessionUri = thatUri.clone(); + this.m_sessionUri.userPass = ""; + // sensible default for user id login: + var username = decodeURIComponent(thatUri.username); + if (username.length > 0) + this.credentials.userId = username; + log("set uri: " + this.uri.spec, this); } } }, - m_userId: null, - get userId() { return this.m_userId; }, + get userId() { return this.credentials.userId; }, - m_defaultCalId: null, get defaultCalId() { - if (this.m_defaultCalId) - return this.m_defaultCalId; - if (!this.isLoggedIn) - return null; - var list = this.getUserPreferences("X-NSCP-WCAP-PREF-icsCalendar", {}); - return ((list && list.length > 0) ? list[0] : this.userId); + var list = this.getUserPreferences("X-NSCP-WCAP-PREF-icsCalendar"); + return (list.length > 0 ? list[0] : this.credentials.userId); }, - assureLoggedIn: - function() - { - if (!this.isLoggedIn) { - throw new Components.Exception( - "Not logged in yet.", - Components.results.NS_ERROR_NOT_AVAILABLE); - } + m_aboutToLogout: false, + get aboutToLogout() { + return this.m_aboutToLogout; }, - get isLoggedIn() { return this.m_sessionId != null; }, - - login: - function() - { - this.log("login"); - this.getSessionId(); // does *not* assure that ticket is valid - }, - - logout: - function() - { - this.log("logout"); - this.m_calPropsTimer.cancel(); // stop any timed calprops getters - this.asyncQueue.reset(); // stop any queued actions - - if (this.m_sessionId) { - this.log( "attempting to log out..." ); - // although io service's offline flag is already - // set BEFORE notification (about to go offline, nsIOService.cpp). - // WTF. - var url = (this.sessionUri.spec + - "logout.wcap?fmt-out=text%2Fxml&id=" + - encodeURIComponent(this.m_sessionId)); - try { - checkWcapXmlErrno( issueSyncXMLRequest(url), - -1 /* logout successfull */ ); - this.log( "logout succeeded." ); - } - catch (exc) { - this.log(exc, "logout failed."); - Components.utils.reportError( exc ); - } - this.m_sessionId = null; - } - - this.m_sessionUri = null; - this.m_userId = null; - this.m_loginUser = null; - this.m_loginPW = null; - this.m_userPrefs = null; // reread prefs - this.m_defaultCalId = null; - this.m_bNoLoginsAnymore = false; - }, - - refresh: - function() - { - if (this.m_bNoLoginsAnymore) - this.logout(); // reset this session - // else the next call will refresh any timed out ticket... - }, - - getWcapErrorString: - function( rc ) - { - return wcapErrorToString(rc); + get isLoggedIn() { + return (this.m_sessionId != null); }, m_defaultCalendar: null, get defaultCalendar() { - if (!this.m_defaultCalendar) { - this.m_defaultCalendar = createWcapCalendar( - null /* calId: null indicates default calendar */, this); - } + if (!this.m_defaultCalendar) + this.m_defaultCalendar = createWcapCalendar(this); return this.m_defaultCalendar; }, - m_calIdToCalendar: {}, - getCalendarByCalId_: - function( calId, bCreate ) - { - var ret = null; - if (!calId || (LOG_LEVEL == 42 && // xxx todo: temp tbe hack - this.defaultCalId == calId)) { - ret = this.defaultCalendar; - } - else { - var key = encodeURIComponent(calId); - ret = this.m_calIdToCalendar[key]; - if (!ret && bCreate) { - ret = createWcapCalendar(calId, this); - this.m_calIdToCalendar[key] = ret; - } - } - return ret; - }, - getCalendarByCalId: - function( calId ) - { - return this.getCalendarByCalId_(calId, true/*bCreate*/); - }, - - getCalendars: - function( out_count, bGetOwnedCals ) - { - var list = this.getUserPreferences( - bGetOwnedCals ? "X-NSCP-WCAP-PREF-icsCalendarOwned" - : "X-NSCP-WCAP-PREF-icsSubscribed", {} ); - var ret = []; - for each( var item in list ) { - var ar = item.split(','); - // ',', '$' are not encoded. ',' can be handled here. WTF. - for each ( a in ar ) { - var dollar = a.indexOf('$'); - if (dollar >= 0) { - ret.push( - this.getCalendarByCalId( a.substring(0, dollar) ) ); - } - } - } - out_count.value = ret.length; - return ret; - }, - getOwnedCalendars: - function( out_count ) - { - return this.getCalendars( out_count, true ); - }, - getSubscribedCalendars: - function( out_count ) - { - return this.getCalendars( out_count, false ); - }, - - createCalendar: - function( calId, name ) - { - try { -// this.assureLoggedIn(); - var url = this.getCommandUrl("createcalendar"); - url += "&allowdoublebook=1&set_calprops=1&subscribe=1"; - url += ("&calid=" + encodeURIComponent(calId)); - // xxx todo: name undocumented! - url += ("&name=" + encodeURIComponent(name)); - // xxx todo: what about categories param??? - this.issueSyncRequest(url + "&fmt-out=text%2Fxml", stringToXml); - this.m_userPrefs = null; // reread prefs - return this.getCalendarByCalId(this.userId + ":" + calId); - } - catch (exc) { - this.logError( exc ); - throw exc; - } - }, - - deleteCalendar: - function( cal ) - { - try { -// this.assureLoggedIn(); - // xxx todo: - cal.assureAccess(Components.interfaces.calIWcapCalendar.AC_FULL); - var calId = cal.calId; - var url = this.getCommandUrl("deletecalendar"); - url += ("&calid=" + encodeURIComponent(calId)); - url += "&unsubscribe=1&fmt-out=text%2Fxml"; - this.issueSyncRequest(url, stringToXml); - this.m_userPrefs = null; // reread prefs - // xxx todo: delete here? - this.m_calIdToCalendar[encodeURIComponent(calId)] = null; - cal.readOnly = true; - } - catch (exc) { - this.logError(exc); - throw exc; - } - }, - - modifyCalendarSubscriptions: - function( cals, bSubscribe ) - { - try { -// this.assureLoggedIn(); - var url = this.getCommandUrl( - bSubscribe ? "subscribe_calendars" : "unsubscribe_calendars" ); - var calId = ""; - for each ( var cal in cals ) { - if (calId.length > 0) - calId += ";"; - calId += encodeURIComponent(cal.calId); - } - url += ("&calid=" + calId); - this.issueSyncRequest(url + "&fmt-out=text%2Fxml", stringToXml); - this.m_userPrefs = null; // reread prefs - for each ( cal in cals ) { - if (bSubscribe) { - this.m_calIdToCalendar[ - encodeURIComponent(cal.calId)] = cal; - } - else { // remove from cached instances - // xxx todo: delete here? - this.m_calIdToCalendar[ - encodeURIComponent(cal.calId)] = null; - } - } - } - catch (exc) { - this.logError( exc ); - throw exc; - } - }, - - subscribeToCalendars: - function( count, cals ) - { - this.modifyCalendarSubscriptions(cals, true/*bSubscribe*/); - }, - - unsubscribeFromCalendars: - function( count, cals ) - { - this.modifyCalendarSubscriptions(cals, false/*!bSubscribe*/); - }, - - m_userPrefs: null, - getUserPreferences: - function( prefName, out_count ) - { - try { - if (!this.m_userPrefs) { -// this.assureLoggedIn(); - var url = this.getCommandUrl( "get_userprefs" ); - url += ("&userid=" + encodeURIComponent(this.userId)); - this.m_userPrefs = this.issueSyncRequest( - url + "&fmt-out=text%2Fxml", stringToXml ); - } - var ret = []; - var nodeList = this.m_userPrefs.getElementsByTagName(prefName); - for ( var i = 0; i < nodeList.length; ++i ) { - var node = nodeList.item(i); - ret.push( trimString(node.textContent) ); - } - out_count.value = ret.length; - return ret; - } - catch (exc) { - this.logError( exc ); - throw exc; - } + getUserPreferences: function calWcapSession_getUserPreferences(prefName) { + var prefs = filterXmlNodes(prefName, this.credentials.userPrefs); + return prefs; }, get defaultAlarmStart() { var alarmStart = null; - var ar = this.getUserPreferences( - "X-NSCP-WCAP-PREF-ceDefaultAlarmStart", {}); + var ar = this.getUserPreferences("X-NSCP-WCAP-PREF-ceDefaultAlarmStart"); if (ar.length > 0 && ar[0].length > 0) { // workarounding cs duration bug, missing "T": var dur = ar[0].replace(/(^P)(\d+[HMS]$)/, "$1T$2"); @@ -1007,12 +832,10 @@ calWcapSession.prototype = { return alarmStart; }, - getDefaultAlarmEmails: - function( out_count ) + getDefaultAlarmEmails: function calWcapSession_getDefaultAlarmEmails(out_count) { var ret = []; - var ar = this.getUserPreferences( - "X-NSCP-WCAP-PREF-ceDefaultAlarmEmail", {}); + var ar = this.getUserPreferences("X-NSCP-WCAP-PREF-ceDefaultAlarmEmail"); if (ar.length > 0 && ar[0].length > 0) { for each ( var i in ar ) { ret = ret.concat( i.split(/[;,]/).map(trimString) ); @@ -1022,290 +845,295 @@ calWcapSession.prototype = { return ret; }, - getFreeBusyTimes_resp: - function( wcapResponse, calId, listener, requestId ) + searchForCalendars: + function calWcapSession_searchForCalendars(searchString, searchOptions, listener) { + var this_ = this; + var request = new calWcapRequest( + function searchForCalendars_resp(request, err, data) { + if (err) + this_.notifyError(err); + if (listener) + listener.onRequestResult(request, data); + }, + log("searchForCalendars, searchString=" + searchString, this)); + try { - var xml = wcapResponse.data; // first statement, may throw - if (listener != null) { - var ret = []; - var nodeList = xml.getElementsByTagName("FB"); - for ( var i = 0; i < nodeList.length; ++i ) { - var node = nodeList.item(i); - var str = node.textContent; - var slash = str.indexOf( '/' ); - var start = getDatetimeFromIcalString(str.substr(0, slash)); - var end = getDatetimeFromIcalString(str.substr(slash + 1)); - var entry = { - isBusyEntry: - (node.attributes.getNamedItem("FBTYPE").nodeValue - == "BUSY"), - dtRangeStart: start, - dtRangeEnd: end - }; - ret.push( entry ); - } - listener.onGetFreeBusyTimes( - Components.results.NS_OK, - requestId, calId, ret.length, ret ); - } - if (LOG_LEVEL > 0) { - this.log( "calId=" + calId + ", " + - getWcapRequestStatusString(xml), - "getFreeBusyTimes_resp()" ); - } - } - catch (exc) { - const calIWcapErrors = Components.interfaces.calIWcapErrors; - switch (getResultCode(exc)) { - case calIWcapErrors.WCAP_NO_ERRNO: // workaround - case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR: - case calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST: - this.log("ignored error: " + errorToString(exc), - "getFreeBusyTimes_resp()"); - break; - default: - this.notifyError( exc ); - break; - } - if (listener != null) - listener.onGetFreeBusyTimes( exc, requestId, calId, 0, [] ); - } - }, - - getFreeBusyTimes_queued: - function( calId, rangeStart, rangeEnd, bBusyOnly, listener, b, requestId ) - { - try { - // assure DATETIMEs: - if (rangeStart != null && rangeStart.isDate) { - rangeStart = rangeStart.clone(); - rangeStart.isDate = false; - } - if (rangeEnd != null && rangeEnd.isDate) { - rangeEnd = rangeEnd.clone(); - rangeEnd.isDate = false; - } - var zRangeStart = getIcalUTC(rangeStart); - var zRangeEnd = getIcalUTC(rangeEnd); - this.log( "\n\trangeStart=" + zRangeStart + - ",\n\trangeEnd=" + zRangeEnd, - "getFreeBusyTimes()" ); - - var url = this.getCommandUrl( "get_freebusy" ); - url += ("&calid=" + encodeURIComponent(calId)); - url += ("&busyonly=" + (bBusyOnly ? "1" : "0")); - url += ("&dtstart=" + zRangeStart); - url += ("&dtend=" + zRangeEnd); - url += "&fmt-out=text%2Fxml"; - - var this_ = this; - function resp( wcapResponse ) { - this_.getFreeBusyTimes_resp( - wcapResponse, calId, listener, requestId ); - } - this.issueAsyncRequest( url, stringToXml, resp ); - } - catch (exc) { - this.notifyError( exc ); - if (listener != null) - listener.onGetFreeBusyTimes( exc, requestId, calId, 0, [] ); - } - }, - - searchForCalendars_resp: - function( wcapResponse, listener, requestId ) - { - try { - var xml = wcapResponse.data; // first statement, may throw - if (listener) { - var ret = []; - var nodeList = xml.getElementsByTagName("iCal"); - for ( var i = 0; i < nodeList.length; ++i ) { - var node = nodeList.item(i); - try { - checkWcapXmlErrno(node); - var ar = filterCalProps( - "X-NSCP-CALPROPS-RELATIVE-CALID", node); - if (ar.length > 0) { - var calId = ar[0]; - // take existing one from subscribed list; - // xxx todo assuming there is an existing instance - // for every subscribed calendar at this point! - var cal = this.getCalendarByCalId_( - calId, false/*bCreate*/); - if (!cal) - cal = createWcapCalendar(calId, this, node); - ret.push(cal); - } - } - catch (exc) { - const calIWcapErrors = Components.interfaces - .calIWcapErrors; - switch (getResultCode(exc)) { - case calIWcapErrors.WCAP_NO_ERRNO: // workaround - case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR: - this.log("ignored error: " + errorToString(exc), - "searchForCalendars_resp()"); - break; - default: - this.notifyError(exc); - break; - } - } - } - this.log("number of found calendars: " + ret.length); - listener.onSearchForCalendarsResults( - Components.results.NS_OK, requestId, ret.length, ret); - } - if (LOG_LEVEL > 0) - this.log("search done."); - } - catch (exc) { - this.notifyError(exc); - if (listener) - listener.onSearchForCalendarsResults(exc, requestId, 0, []); - } - }, - - searchForCalendars_queued: - function( searchString, searchOptions, listener, requestId ) - { - try { - var url = this.getCommandUrl("search_calprops"); - url += ("&search-string=" + encodeURIComponent(searchString)); - url += ("&searchOpts=" + (searchOptions & 3).toString(10)); - const calIWcapSession = Components.interfaces.calIWcapSession; + var params = ("&fmt-out=text%2Fxml&search-string=" + + encodeURIComponent(searchString)); + params += ("&searchOpts=" + (searchOptions & 3).toString(10)); if (searchOptions & calIWcapSession.SEARCH_INCLUDE_CALID) - url += "&calid=1"; + params += "&calid=1"; if (searchOptions & calIWcapSession.SEARCH_INCLUDE_NAME) - url += "&name=1"; + params += "&name=1"; if (searchOptions & calIWcapSession.SEARCH_INCLUDE_OWNER) - url += "&primaryOwner=1"; - url += "&fmt-out=text%2Fxml"; + params += "&primaryOwner=1"; - var this_ = this; - this.issueAsyncRequest( - url, - // string to xml converter func without WCAP errno check: - function(data) { - if (!data || data == "") { // assuming time-out + this.issueNetworkRequest( + request, + function searchForCalendars_netResp(err, data) { + if (err) + throw err; + // string to xml converter func without WCAP errno check: + if (!data || data.length == 0) { // assuming time-out throw new Components.Exception( "Login failed. Invalid session ID.", - Components.interfaces.calIWcapErrors - .WCAP_LOGIN_FAILED ); + calIWcapErrors.WCAP_LOGIN_FAILED); } - return getDomParser().parseFromString(data, "text/xml"); + var xml = getDomParser().parseFromString(data, "text/xml"); + var ret = []; + var nodeList = xml.getElementsByTagName("iCal"); + for ( var i = 0; i < nodeList.length; ++i ) { + var node = nodeList.item(i); + try { + checkWcapXmlErrno(node); + var ar = filterXmlNodes("X-NSCP-CALPROPS-RELATIVE-CALID", node); + if (ar.length > 0) { + var calId = ar[0]; + var cal = this_.m_subscribedCals[encodeURIComponent(calId)]; + if (!cal) { + if (calId == this_.defaultCalId) + cal = this_.defaultCalendar; + else + cal = createWcapCalendar(this_, node); + } + ret.push(cal); + } + } + catch (exc) { + switch (getResultCode(exc)) { + case calIWcapErrors.WCAP_NO_ERRNO: // workaround + case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR: + log("searchForCalendars_netResp() ignored error: " + + errorToString(exc), this_); + break; + default: + this_.notifyError(exc); + break; + } + } + } + log("search done. number of found calendars: " + ret.length, this_); + request.execRespFunc(null, ret); }, - // response func: - function(wcapResponse) { - this_.searchForCalendars_resp( - wcapResponse, listener, requestId); - } ); + function identity(data) { return data; }, + "search_calprops", params); } catch (exc) { - this.notifyError(exc); - if (listener) - listener.onSearchForCalendarsResults(exc, requestId, 0, []); + request.execRespFunc(exc); } + return request; + }, + + getFreeBusyTimes: function calWcapCalendar_getFreeBusyTimes( + calId, rangeStart, rangeEnd, bBusy, listener) + { + // assure DATETIMEs: + if (rangeStart && rangeStart.isDate) { + rangeStart = rangeStart.clone(); + rangeStart.isDate = false; + } + if (rangeEnd && rangeEnd.isDate) { + rangeEnd = rangeEnd.clone(); + rangeEnd.isDate = false; + } + var zRangeStart = getIcalUTC(rangeStart); + var zRangeEnd = getIcalUTC(rangeEnd); + + var this_ = this; + var request = new calWcapRequest( + function getFreeBusyTimes_resp(request, err, data) { + switch (getResultCode(err)) { + case Components.results.NS_OK: + break; + case calIWcapErrors.WCAP_NO_ERRNO: // workaround + case calIWcapErrors.WCAP_ACCESS_DENIED_TO_CALENDAR: + case calIWcapErrors.WCAP_CALENDAR_DOES_NOT_EXIST: + log("getFreeBusyTimes_resp() error: " + errorToString(err), this_); + break; + default: + this_.notifyError(err); + break; + } + if (listener) + listener.onRequestResult(request, data); + }, + log("getFreeBusyTimes():\n\tcalId=" + calId + + "\n\trangeStart=" + zRangeStart + ",\n\trangeEnd=" + zRangeEnd, this)); + + try { + var params = ("&calid=" + encodeURIComponent(calId)); + params += ("&busyonly=" + (bBusy ? "1" : "0")); + params += ("&dtstart=" + zRangeStart); + params += ("&dtend=" + zRangeEnd); + params += "&fmt-out=text%2Fxml"; + + this.issueNetworkRequest( + request, + function getFreeBusyTimes_resp(err, xml) { + if (err) + throw err; + if (LOG_LEVEL > 0) { + log("getFreeBusyTimes_resp(): " + + getWcapRequestStatusString(xml), this_); + } + if (listener) { + var ret = []; + var nodeList = xml.getElementsByTagName("FB"); + for ( var i = 0; i < nodeList.length; ++i ) { + var node = nodeList.item(i); + if ((node.attributes.getNamedItem("FBTYPE").nodeValue + == "BUSY") != bBusy) { + continue; + } + var str = node.textContent; + var slash = str.indexOf('/'); + var period = new CalPeriod(); + period.start = getDatetimeFromIcalString(str.substr(0, slash)); + period.end = getDatetimeFromIcalString(str.substr(slash + 1)); + period.makeImmutable(); + ret.push(period); + } + request.execRespFunc(null, ret); + } + }, + stringToXml, "get_freebusy", params); + } + catch (exc) { + request.execRespFunc(exc); + } + return request; }, m_bInstalledLogoutObservers: false, assureInstalledLogoutObservers: - function() + function calWcapSession_assureInstalledLogoutObservers() { // don't do this in ctor, calendar manager calls back to all // registered calendars! if (!this.m_bInstalledLogoutObservers) { this.m_bInstalledLogoutObservers = true; // listen for shutdown, being logged out: - // network:offline-about-to-go-offline will be fired for - // XPCOM shutdown, too. - // xxx todo: alternatively, add shutdown notifications to - // cal manager - // xxx todo: how to simplify this for multiple topics? - var observerService = - Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.addObserver( - this, "quit-application", - false /* don't hold weakly: xxx todo */); - observerService.addObserver( - this, "network:offline-about-to-go-offline", - false /* don't hold weakly: xxx todo */); + var observerService = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + observerService.addObserver(this, "quit-application", false /* don't hold weakly */); getCalendarManager().addObserver(this); } }, // nsIObserver: - observe: - function( subject, topic, data ) + observe: function calWcapSession_observer(subject, topic, data) { - this.log( "observing: " + topic + ", data: " + data ); - if (topic == "network:offline-about-to-go-offline") { - this.logout(); - } - else if (topic == "quit-application") { - this.logout(); + log("observing: " + topic + ", data: " + data, this); + if (topic == "quit-application") { + g_bShutdown = true; + this.logout(null); // xxx todo: valid upon notification? getCalendarManager().removeObserver(this); - var observerService = - Components.classes["@mozilla.org/observer-service;1"] - .getService(Components.interfaces.nsIObserverService); - observerService.removeObserver( - this, "quit-application" ); - observerService.removeObserver( - this, "network:offline-about-to-go-offline" ); + var observerService = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + observerService.removeObserver(this, "quit-application"); } }, + modifySubscriptions: function calWcapSession_modifySubscriptions(cal, bSubscribe) + { + var wcapCommand = ((bSubscribe ? "" : "un") + "subscribe_calendars"); + var this_ = this; + var request = new calWcapRequest( + function subscr_resp(request, err) { + if (err) + this_.notifyError(err); + }, + log(wcapCommand + ": " + cal.calId)); + this.issueNetworkRequest( + request, + function netResp(err, xml) { + if (err) + throw err; + }, + stringToXml, wcapCommand, + "&fmt-out=text%2Fxml&calid=" + encodeURIComponent(cal.calId)); + return request; + }, + // calICalendarManagerObserver: // called after the calendar is registered - onCalendarRegistered: - function( cal ) + onCalendarRegistered: function calWcapSession_onCalendarRegistered(cal) { + try { + cal = cal.QueryInterface(calIWcapCalendar); + } + catch (exc) { + cal = null; + } + try { + // make sure the calendar belongs to this session: + if (cal && cal.session.uri.equals(this.uri)) { + if (!cal.isDefaultCalendar) { + var key = encodeURIComponent(cal.calId); + if (!this.m_subscribedCals[key]) + this.modifySubscriptions(cal, true/*bSubscibe*/); + this.m_subscribedCals[key] = cal; + } + } + } + catch (exc) { // never break the listener chain + this.notifyError(exc); + } }, // called before the unregister actually takes place - onCalendarUnregistering: - function( cal ) + onCalendarUnregistering: function calWcapSession_onCalendarUnregistering(cal) { try { - cal = cal.QueryInterface(Components.interfaces.calIWcapCalendar); - if (cal && cal.session.wrappedJSObject == this && - cal.calId == this.defaultCalId) { - // calendar is to be deleted, so logout before: - this.logout(); - } + cal = cal.QueryInterface(calIWcapCalendar); } catch (exc) { + cal = null; + } + try { + // make sure the calendar belongs to this session: + if (cal && cal.session.uri.equals(this.uri)) { + if (cal.isDefaultCalendar) { + // whole account is being removed, so logout before: + this.logout(null); + } + else { + var key = encodeURIComponent(cal.calId); + if (this.m_subscribedCals[key]) { + delete this.m_subscribedCals[key]; + this.modifySubscriptions(cal, false/*bSubscibe*/); + } + } + } + } + catch (exc) { // never break the listener chain + this.notifyError(exc); } }, // called before the delete actually takes place - onCalendarDeleting: - function( cal ) + onCalendarDeleting: function calWcapSession_onCalendarDeleting(cal) { }, // called after the pref is set - onCalendarPrefSet: - function( cal, name, value ) + onCalendarPrefSet: function calWcapSession_onCalendarPrefSet(cal, name, value) { }, // called before the pref is deleted - onCalendarPrefDeleting: - function( cal, name ) + onCalendarPrefDeleting: function calWcapSession_onCalendarPrefDeleting(cal, name) { } }; var g_confirmedHttpLogins = null; -function confirmInsecureLogin( uri ) +function confirmInsecureLogin(uri) { - if (g_confirmedHttpLogins == null) { + if (!g_confirmedHttpLogins) { g_confirmedHttpLogins = {}; var confirmedHttpLogins = getPref( "calendar.wcap.confirmed_http_logins", ""); @@ -1322,19 +1150,19 @@ function confirmInsecureLogin( uri ) var encodedHost = encodeURIComponent(host); var confirmedEntry = g_confirmedHttpLogins[encodedHost]; if (confirmedEntry) { - bConfirmed = (confirmedEntry == "1" ? true : false); + bConfirmed = (confirmedEntry == "1"); } else { var prompt = getWindowWatcher().getNewPrompter(null); var bundle = getWcapBundle(); - var dontAskAgain = { value: false }; + var out_dontAskAgain = { value: false }; var bConfirmed = prompt.confirmCheck( bundle.GetStringFromName("noHttpsConfirmation.label"), bundle.formatStringFromName("noHttpsConfirmation.text", [host], 1), bundle.GetStringFromName("noHttpsConfirmation.check.text"), - dontAskAgain ); + out_dontAskAgain); - if (dontAskAgain.value) { + if (out_dontAskAgain.value) { // save decision for all running calendars and // all future confirmations: var confirmedHttpLogins = getPref( @@ -1348,7 +1176,7 @@ function confirmInsecureLogin( uri ) } } - logMessage("confirmInsecureLogin(" + host + ")", "returned: " + bConfirmed); + log("returned: " + bConfirmed, "confirmInsecureLogin(" + host + ")"); return bConfirmed; } diff --git a/calendar/providers/wcap/calWcapUtils.js b/calendar/providers/wcap/calWcapUtils.js index fb5bf633e87..9b9fc86dafc 100644 --- a/calendar/providers/wcap/calWcapUtils.js +++ b/calendar/providers/wcap/calWcapUtils.js @@ -37,121 +37,212 @@ * * ***** END LICENSE BLOCK ***** */ -function logMessage( context, msg ) +var g_bShutdown = false; +var g_logTimezone = null; +var g_logFilestream = null; +var g_logPrefObserver = null; + +function initLogging() { + g_logTimezone = getPref("calendar.timezone.local", null); + + if (g_logFilestream) { + try { + g_logFilestream.close(); + } + catch (exc) { + } + g_logFilestream = null; + } + + LOG_LEVEL = getPref("calendar.wcap.log_level", 0); + if (LOG_LEVEL < 1 && getPref("calendar.debug.log", false)) + LOG_LEVEL = 1; // at least basic logging when calendar.debug.log is set + if (LOG_LEVEL > 0) { + var logFileName = getPref("calendar.wcap.log_file", null); + if (logFileName) { + try { + // set up file: + var logFile = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + logFile.initWithPath(logFileName); + // create output stream: + var logFileStream = + Components.classes["@mozilla.org/network/file-output-stream;1"] + .createInstance(Components.interfaces.nsIFileOutputStream); + logFileStream.init( + logFile, + 0x02 /* PR_WRONLY */ | + 0x08 /* PR_CREATE_FILE */ | + 0x10 /* PR_APPEND */, + 0700 /* read, write, execute/search by owner */, + 0 /* unused */); + g_logFilestream = logFileStream; + } + catch (exc) { + logError(exc, "init logging"); + } + } + log("################################# NEW LOG #################################", + "init logging"); + } + if (!g_logPrefObserver) { + g_logPrefObserver = { // nsIObserver: + observe: function logPrefObserver_observe(subject, topic, data) { + if (topic == "nsPref:changed") { + switch (data) { + case "calendar.wcap.log_level": + case "calendar.wcap.log_file": + case "calendar.debug.log": + initLogging(); + break; + } + } + } + }; + var prefBranch = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch2); + prefBranch.addObserver("calendar.wcap.log_level", g_logPrefObserver, false); + prefBranch.addObserver("calendar.wcap.log_file", g_logPrefObserver, false); + prefBranch.addObserver("calendar.debug.log", g_logPrefObserver, false); + + var observerService = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + var appObserver = { // nsIObserver: + observe: function app_observe(subject, topic, data) { + if (topic == "quit-application") + prefBranch.removeObserver("calendar.", g_logPrefObserver); + } + }; + observerService.addObserver(appObserver, "quit-application", false); + } +} + +function log(msg, context, bForce) +{ + if (bForce || LOG_LEVEL > 0) { + var ret = ""; + if (context) + ret += ("[" + context + "]"); + if (msg) { + if (ret.length > 0) + ret += "\n"; + ret += msg; + } var now = getTime(); - if (LOG_TIMEZONE != null) - now = now.getInTimezone(LOG_TIMEZONE); - var str = ("\n### WCAP log " + now + "\n### [" + context + "]\n### " + - (msg ? msg : "")); - getConsoleService().logStringMessage( str ); - str += "\n\n"; - dump( str ); - if (LOG_FILE_STREAM != null) { + if (now && g_logTimezone) + now = now.getInTimezone(g_logTimezone); + var str = ("### WCAP log entry: " + now + "\n" + ret); + getConsoleService().logStringMessage(str); + str = ("\n" + str + "\n"); + dump(str); + if (g_logFilestream) { try { // xxx todo? // assuming ANSI chars here, for logging sufficient: - LOG_FILE_STREAM.write( str, str.length ); + g_logFilestream.write(str, str.length); } catch (exc) { // catching any io errors here: - var err = ("error writing log file: " + exc); - Components.utils.reportError( exc ); - getConsoleService().logStringMessage( err ); - dump( err + "\n\n" ); + var err = ("error writing log file: " + errorToString(exc)); + Components.utils.reportError(exc); + getConsoleService().logStringMessage(err); + dump(err + "\n\n"); } } - return str; + return ret; } else return msg; } -// late-init service accessors: +function logError(err, context) +{ + var msg = errorToString(err); + Components.utils.reportError( log("error: " + msg, context, true) ); + return msg; +} + +// late-inited service accessors: var g_consoleService = null; -function getConsoleService() -{ - if (g_consoleService == null) { - g_consoleService = Components.classes["@mozilla.org/consoleservice;1"] - .getService(Components.interfaces.nsIConsoleService); +function getConsoleService() { + if (!g_consoleService) { + g_consoleService = + Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); } return g_consoleService; } var g_windowWatcher = null; -function getWindowWatcher() -{ - if (g_windowWatcher == null) { +function getWindowWatcher() { + if (!g_windowWatcher) { g_windowWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] - .getService(Components.interfaces.nsIWindowWatcher); + .getService(Components.interfaces.nsIWindowWatcher); } return g_windowWatcher; } var g_icsService = null; -function getIcsService() -{ - if (g_icsService == null) { - g_icsService = Components.classes["@mozilla.org/calendar/ics-service;1"] - .getService(Components.interfaces.calIICSService); +function getIcsService() { + if (!g_icsService) { + g_icsService = + Components.classes["@mozilla.org/calendar/ics-service;1"] + .getService(Components.interfaces.calIICSService); } return g_icsService; } var g_domParser = null; -function getDomParser() -{ - if (g_domParser == null) { - g_domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"] +function getDomParser() { + if (!g_domParser) { + g_domParser = + Components.classes["@mozilla.org/xmlextras/domparser;1"] .getService(Components.interfaces.nsIDOMParser); } return g_domParser; } var g_calendarManager = null; -function getCalendarManager() -{ - if (g_calendarManager == null) { +function getCalendarManager() { + if (!g_calendarManager) { g_calendarManager = Components.classes["@mozilla.org/calendar/manager;1"] - .getService(Components.interfaces.calICalendarManager); + .getService(Components.interfaces.calICalendarManager); } return g_calendarManager; }; var g_wcapBundle = null; -function getWcapBundle() -{ - if (g_wcapBundle == null) { +function getWcapBundle() { + if (!g_wcapBundle) { var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService); + .getService(Components.interfaces.nsIStringBundleService); g_wcapBundle = stringBundleService.createBundle( - "chrome://calendar/locale/wcap.properties" ); + "chrome://calendar/locale/wcap.properties"); } return g_wcapBundle; } -function getResultCode( exc ) -{ - return (exc instanceof Components.interfaces.nsIException - ? exc.result : exc); +function subClass(subCtor, baseCtor) { + subCtor.prototype = new baseCtor(); + subCtor.prototype.constructor = subCtor; + subCtor.prototype.superClass = baseCtor; } -function testResultCode( exc, rc ) -{ - return (getResultCode(exc) == rc); +function qiface(list, iid) { + if (!list.some( function(i) { return i.equals(iid); } )) + throw Components.results.NS_ERROR_NO_INTERFACE; } -function isEvent( item ) -{ +function isEvent(item) { return (item instanceof Components.interfaces.calIEvent); } -function isParent( item ) -{ +function isParent(item) { if (item.id != item.parentItem.id) { throw new Components.Exception( "proxy has different id than its parent!"); @@ -159,7 +250,7 @@ function isParent( item ) return (!item.recurrenceId); } -function forEachIcalComponent( icalRootComp, componentType, func, maxResult ) +function forEachIcalComponent(icalRootComp, componentType, func, maxResult) { var itemCount = 0; // libical returns the vcalendar component if there is just @@ -182,11 +273,11 @@ function forEachIcalComponent( icalRootComp, componentType, func, maxResult ) } } -function filterCalProps(propName, calProps) +function filterXmlNodes(name, rootNode) { var ret = []; - if (calProps) { - var nodeList = calProps.getElementsByTagName(propName); + if (rootNode) { + var nodeList = rootNode.getElementsByTagName(name); for ( var i = 0; i < nodeList.length; ++i ) { var node = nodeList.item(i); ret.push( trimString(node.textContent) ); @@ -195,20 +286,19 @@ function filterCalProps(propName, calProps) return ret; } -function trimString( str ) -{ +function trimString(str) { return str.replace( /(^\s+|\s+$)/g, "" ); } -function getTime() -{ +function getTime() { + if (g_bShutdown) + return null; var ret = new CalDateTime(); ret.jsDate = new Date(); return ret; } -function getIcalUTC( dt ) -{ +function getIcalUTC(dt) { if (!dt) return "0"; else { @@ -220,52 +310,63 @@ function getIcalUTC( dt ) } } -function getDatetimeFromIcalString( val ) -{ +function getDatetimeFromIcalString(val) { if (!val || val.length == 0 || val == "0") return null; // assuming timezone is known: var dt = new CalDateTime(); dt.icalString = val; -// if (dt.icalString != val) -// logMessage("date-time error", dt.icalString + " vs. " + val); + if (LOG_LEVEL > 1) { + var dt_ = dt.clone(); + dt_.normalize(); + if (dt.icalString != val || dt_.icalString != val) { + logError(dt.icalString + " vs. " + val, "date-time error"); + logError(dt_.icalString + " vs. " + val, "date-time error"); + debugger; + } + } return dt; } -function getDatetimeFromIcalProp( prop ) -{ +function getDatetimeFromIcalProp(prop) { if (!prop) return null; return getDatetimeFromIcalString(prop.valueAsIcalString); } -function getPref(prefName, defaultValue) -{ +function getPref(prefName, defaultValue) { const nsIPrefBranch = Components.interfaces.nsIPrefBranch; var prefBranch = Components.classes["@mozilla.org/preferences-service;1"] - .getService(nsIPrefBranch); + .getService(nsIPrefBranch); + var ret; try { switch (prefBranch.getPrefType(prefName)) { case nsIPrefBranch.PREF_BOOL: - return prefBranch.getBoolPref(prefName); + ret = prefBranch.getBoolPref(prefName); + break; case nsIPrefBranch.PREF_INT: - return prefBranch.getIntPref(prefName); + ret = prefBranch.getIntPref(prefName); + break; case nsIPrefBranch.PREF_STRING: - return prefBranch.getCharPref(prefName); + ret = prefBranch.getCharPref(prefName); + break; default: - return defaultValue; + ret = defaultValue; + break; } } catch (exc) { - return defaultValue; + ret = defaultValue; } + log(ret, "getPref(): prefName=" + prefName); + return ret; } -function setPref(prefName, value) -{ +function setPref(prefName, value) { + log(value, "setPref(): prefName=" + prefName); const nsIPrefBranch = Components.interfaces.nsIPrefBranch; var prefBranch = Components.classes["@mozilla.org/preferences-service;1"] - .getService(nsIPrefBranch); + .getService(nsIPrefBranch); switch (typeof(value)) { case "boolean": prefBranch.setBoolPref(prefName, value); @@ -282,123 +383,3 @@ function setPref(prefName, value) } } -function AsyncQueue() -{ - this.wrappedJSObject = this; - this.m_queue = []; -} -AsyncQueue.prototype = { - m_queue: null, - - reset: - function() - { - this.m_queue = []; - }, - - m_proxy: null, - get proxy() { - if (!this.m_proxy) { - var eventTarget = null; - try { - var eventQueueService = - Components.classes["@mozilla.org/event-queue-service;1"] - .getService(Components.interfaces.nsIEventQueueService); - eventTarget = eventQueueService.createThreadEventQueue( - Components.classes["@mozilla.org/thread;1"] - .createInstance(Components.interfaces.nsIThread), true); - } - catch (exc) { // eventQueue has vanished on trunk: - var threadManager = - Components.classes["@mozilla.org/thread-manager;1"] - .getService(Components.interfaces.nsIThreadManager); - eventTarget = threadManager.newThread(0); - } - var proxyMgr = Components.classes["@mozilla.org/xpcomproxy;1"] - .getService(Components.interfaces.nsIProxyObjectManager); - this.m_proxy = proxyMgr.getProxyForObject( - eventTarget, Components.interfaces.nsIRunnable, this, - Components.interfaces.nsIProxyObjectManager.INVOKE_ASYNC ); - } - return this.m_proxy; - }, - - queuedExec: - function(func) - { - this.m_queue.push(func); - if (LOG_LEVEL > 1) - logMessage("enqueued: q=" + this.m_queue.length); - if (this.m_queue.length == 1) { - this.proxy.run(); // empty queue - } - }, - - m_ifaces: [ Components.interfaces.nsIRunnable, - Components.interfaces.nsIClassInfo, - Components.interfaces.nsISupports ], - - // nsISupports: - QueryInterface: - function( iid ) - { - for each ( var iface in this.m_ifaces ) { - if (iid.equals( iface )) - return this; - } - throw Components.results.NS_ERROR_NO_INTERFACE; - }, - - // nsIClassInfo: - getInterfaces: function( count ) { - count.value = this.m_ifaces.length; - return this.m_ifaces; - }, - - get classDescription() { - return "Async Queue"; - }, - get contractID() { - return "@mozilla.org/calendar/calendar/wcap/async-queue;1"; - }, - get classID() { - return Components.ID("{C50F7442-C43E-43f6-AA3F-1ADB87E7A962}"); - }, - getHelperForLanguage: function( language ) { return null; }, - implementationLanguage: - Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT, - flags: 0, - - // nsIRunnable: - run: - function() { - while (this.m_queue.length > 0) { - if (LOG_LEVEL > 1) - logMessage("queue exec: " + this.m_queue.length); - try { - this.m_queue[0](); - } - catch (exc) { // swallow all exceptions, - // they does not belong to this call - debugger; - var msg = errorToString(exc); - Components.utils.reportError( - logMessage("error: " + msg, "swallowed exception") ); - } - // don't remove element unless func has been executed: - this.m_queue.shift(); - if (LOG_LEVEL > 1) - logMessage("dequeued: " + this.m_queue.length); - } - } -}; - -function makeQueuedCall(asyncQueue, obj, func) -{ - return function() { - var args = []; - for ( var i = 0; i < arguments.length; ++i ) - args.push(arguments[i]); - asyncQueue.queuedExec( function() { func.apply(obj, args); } ); - } -} diff --git a/calendar/providers/wcap/public/Makefile.in b/calendar/providers/wcap/public/Makefile.in index e51fddba82f..65bbc01c792 100644 --- a/calendar/providers/wcap/public/Makefile.in +++ b/calendar/providers/wcap/public/Makefile.in @@ -48,11 +48,9 @@ MODULE = wcap XPIDL_MODULE = wcap XPIDLSRCS = calIWcapCalendar.idl \ - calIWcapErrors.idl \ - calIWcapFreeBusyEntry.idl \ - calIWcapFreeBusyListener.idl \ - calIWcapSearchListener.idl \ calIWcapSession.idl \ + calIWcapErrors.idl \ + calIWcapRequest.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/calendar/providers/wcap/public/calIWcapCalendar.idl b/calendar/providers/wcap/public/calIWcapCalendar.idl index 0667ccd6589..b8677be1af8 100644 --- a/calendar/providers/wcap/public/calIWcapCalendar.idl +++ b/calendar/providers/wcap/public/calIWcapCalendar.idl @@ -39,9 +39,11 @@ #include "calICalendar.idl" #include "calIDateTime.idl" +#include "calIWcapRequest.idl" interface calIWcapSession; +interface calIAttendee; -/** Adds WCAP specific capabilities. +/** Adds WCAP specific capabilities to calICalendar. */ [scriptable, uuid(21A189DF-6C92-41f6-9E2B-1929EF25CAEE)] interface calIWcapCalendar : calICalendar @@ -53,46 +55,75 @@ interface calIWcapCalendar : calICalendar /** * Current calId the calendar instance acts on; defaults to userId. - * @exception may throw an NS_ERROR_NOT_AVAILABLE of not logged in + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string calId; - + /** * UserId of primary owner of this calendar instance. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string ownerId; + /** + * Determines whether this calendar instance is the user's default calendar. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in + */ + readonly attribute boolean isDefaultCalendar; + /** * Whether the currently selected calendar belongs to user. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute boolean isOwnedCalendar; /** * Calendar description. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string description; /** * Calendar display name. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string displayName; /** * Gets this calendar's (calId) default timezone. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string defaultTimezone; + /** + * Tests whether the passed item corresponds to an invitation. + * + * @param item item to be tested + * @return whether the passed item corresponds to an invitation + */ + boolean isInvitation(in calIItemBase item); + + /** + * Gets the invited attendee if the passed item corresponds to + * an invitation. + * + * @param item invitation item + * @return attendee object else null + */ + calIAttendee getInvitedAttendee(in calIItemBase item); + /** * Gets calendar properties. - * An error is notified to all registered calIObservers, then thrown. * * @param propName property name (e.g. X-S1CS-CALPROPS-COMMON-NAME) - * @return array of property values + * @param count length of props array + * @param listener called with array of strings as result + * @return request object to track operation + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ - void getCalendarProperties( - in string propName, - out unsigned long count, - [array, size_is(count), retval] out string properties ); + void getCalendarProperties(in string propName, + out unsigned long count, + [array, size_is(count), retval] out string props); /* xxx todo: additional filters sensible for calICalendar, too? claiming bits 24-30 for now. @@ -180,12 +211,13 @@ interface calIWcapCalendar : calICalendar * @param listener operation listener for SYNC operation * (may optionally implemented calIObserver to receive * onAddItem(), onModifyItem() or onDeleteItem() calls) + * @return request object to track operation */ - void syncChangesTo( + calIWcapRequest syncChangesTo( in calICalendar destCal, in unsigned long itemFilter, in calIDateTime dtFrom, - in calIOperationListener listener ); + in calIOperationListener listener); /* xxx todo: separate out into another interface and leave only an attribute @@ -262,10 +294,11 @@ interface calIWcapCalendar : calICalendar * if (cal.checkAccess(calIWcapCalendar.AC_PROP_WRITE)) * cal.name = newName; * + * @exception NS_ERROR_NOT_AVAILABLE if not logged in * @param accessControlBits access control bits (above AC_ definitions) * @return true if access is granted, false otherwise */ - boolean checkAccess( in unsigned long accessControlBits ); + boolean checkAccess(in unsigned long accessControlBits); /** * Defines granted and denied permissions for a specific user or @@ -288,9 +321,12 @@ interface calIWcapCalendar : calICalendar * - @ stands in for everybody * xxx todo: change the above * @param accessControlBits access control bits (above AC_ definitions) + * @param listener called when access control bits have been updated + * @return request object to track operation */ - void defineAccessControl( - in string userId, in unsigned long accessControlBits ); + calIWcapRequest defineAccessControl( + in string userId, in unsigned long accessControlBits, + in calIWcapRequestResultListener listener); /** * To reset a user's access control definition to the default ones @@ -299,20 +335,26 @@ interface calIWcapCalendar : calICalendar * Components.results.NS_ERROR_INVALID_ARG is thrown. * * @param userId user id + * @param listener called when access control bits have been updated + * @return request object to track operation */ - void resetAccessControl( in string userId ); + calIWcapRequest resetAccessControl( + in string userId, + in calIWcapRequestResultListener listener); - /** - * Gets the set of access control definitions (including "everybody"). - * Both out arrays have the same length. - * - * @param count length of returned arrays - * @param users users ids - * @param accessControlBits access control bits - */ - void getAccessControlDefinitions( - out unsigned long count, - [array, size_is(count)] out string users, - [array, size_is(count)] out unsigned long accessControlBits ); +// /** +// * Gets the set of access control definitions (including "everybody"). +// * Both out arrays have the same length. +// * +// * @param count length of returned arrays +// * @param users users ids +// * @param accessControlBits access control bits +// * @param listener called with xxx todo +// * @return request object to track operation +// */ +// calIWcapRequest getAccessControlDefinitions( +// out unsigned long count, +// [array, size_is(count)] out string users, +// [array, size_is(count)] out unsigned long accessControlBits ); }; diff --git a/calendar/providers/wcap/public/calIWcapFreeBusyEntry.idl b/calendar/providers/wcap/public/calIWcapFreeBusyEntry.idl deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/calendar/providers/wcap/public/calIWcapFreeBusyListener.idl b/calendar/providers/wcap/public/calIWcapFreeBusyListener.idl deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/calendar/providers/wcap/public/calIWcapRequest.idl b/calendar/providers/wcap/public/calIWcapRequest.idl new file mode 100755 index 00000000000..cc157ae4592 --- /dev/null +++ b/calendar/providers/wcap/public/calIWcapRequest.idl @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 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 mozilla.org code. + * + * The Initial Developer of the Original Code is Sun Microsystems, Inc. + * Portions created by Sun Microsystems are Copyright (C) 2006 Sun + * Microsystems, Inc. All Rights Reserved. + * + * Original Author: Daniel Boelzle (daniel.boelzle@sun.com) + * + * Contributor(s): + * + * + * 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIVariant.idl" + +[scriptable, uuid(6586B48D-3FF6-4b82-B9F6-5B561D416E36)] +interface calIWcapRequest : nsISupports +{ + /** + * For easy testing for equality. + */ + readonly attribute unsigned long id; + + /** + * Determines whether the request is pending, i.e. has not been completed. + */ + readonly attribute boolean isPending; + + /** + * Determines whether the request has succeeded, i.e. it has successfully + * been completed. + * XXX todo: remove this and favor + * !request.isPending && Components.isSuccessCode(request.status) ? + */ + readonly attribute boolean succeeded; + + /** + * Status of the request, e.g. NS_OK while pending or after successful + * completion, or NS_ERROR_FAILED when failed. + */ + readonly attribute nsIVariant status; + + /** + * Cancels a pending request and changes status. + */ + void cancel(in nsIVariant status); +}; + +[scriptable, uuid(D0BC007F-D0B5-4352-A32A-8F7A9F55A713)] +interface calIWcapRequestResultListener : nsISupports +{ + /** + * Callback receiving results. + * + * @param request object to track operation + * @param result request result or null in case of an error + */ + void onRequestResult(in calIWcapRequest request, + in nsIVariant result); +}; + diff --git a/calendar/providers/wcap/public/calIWcapSearchListener.idl b/calendar/providers/wcap/public/calIWcapSearchListener.idl deleted file mode 100755 index e69de29bb2d..00000000000 diff --git a/calendar/providers/wcap/public/calIWcapSession.idl b/calendar/providers/wcap/public/calIWcapSession.idl index d44244384b5..bf44aeddec2 100755 --- a/calendar/providers/wcap/public/calIWcapSession.idl +++ b/calendar/providers/wcap/public/calIWcapSession.idl @@ -38,9 +38,8 @@ * ***** END LICENSE BLOCK ***** */ #include "calIWcapCalendar.idl" -#include "calIWcapFreeBusyListener.idl" -#include "calIWcapSearchListener.idl" -#include "calIDuration.idl" +#include "calIDateTime.idl" +#include "calIWcapRequest.idl" /** * WCAP session. @@ -49,14 +48,13 @@ interface calIWcapSession : nsISupports { /** - * Setting this URI causes the session to be disconnected. + * Setting this URI causes the session to logged out and disconnected. */ attribute nsIURI uri; /** * User that has established this session. - * Reading this attribute prompts for login if the session has not yet - * been established. + * @exception NS_ERROR_NOT_AVAILABLE if not logged in */ readonly attribute string userId; @@ -65,67 +63,12 @@ interface calIWcapSession : nsISupports * Does _not_ check whether the user's ticket has timed out! */ readonly attribute boolean isLoggedIn; - - /** - * Explicitly performs a session establishment if the user is not logged - * in already. - * Commonly not needed, because any attempt to get a calendar instance - * will establish a session automatically. - * UI will prompt for a userId and password. - */ - void login(); - - /** - * Explicitly performs a session logout. - * Commonly not needed, because the user will be logged out upon - * "network:offline-about-to-go-offline" and "quit-application" - * automatically. - */ - void logout(); - - /** - * Gets a text for an error code. - * - * @param rc error code defined in calIWcapErrors - * @return error string - * @exception NS_ERROR_INVALID_ARG - */ - string getWcapErrorString( in unsigned long rc ); - /** - * The user's default calendar. + * Gets the default calendar instance of this session. */ readonly attribute calIWcapCalendar defaultCalendar; - /** - * Gets a calendar instance for the passed calId using this session. - * - * @param calId full calId (incl. ":") - * @return calendar instance - */ - calIWcapCalendar getCalendarByCalId( in string calId ); - - /** - * Gets calendars where the user is the primary owner - * (including default calendar). - * - * @return array of owned calendars - */ - void getOwnedCalendars( - out unsigned long count, - [array, size_is(count), retval] out calIWcapCalendar ownedCals ); - - /** - * Gets subscribed calendars (may include calendars where the user - * is the primary owner). - * - * @return array of subscribed calendars - */ - void getSubscribedCalendars( - out unsigned long count, - [array, size_is(count), retval] out calIWcapCalendar subscribedCals ); - /** * Specifies how to match the searchString. */ @@ -143,113 +86,36 @@ interface calIWcapSession : nsISupports /* xxx todo searching: separate into own interface? */ /** * Searches for calendars matching the specified searchString. - * Results are notified to the passed listener instance. - * An error is notified to all registered calIObservers and - * to calIWcapSearchListener::onGetFreeBusyTimes with rc != NS_OK. - * The returned count of calendars (with respect to Sun calendar - * servers) is limited to 200. + * Results are notified to the passed listener instance as + * an array of calendar instances. + * The maximum returned count of calendars + * (with respect to Sun calendar servers) is limited to 200. * * @param searchString the search string to match * @param searchOptions the search options - * @param listener listener receiving results - * @param requestId request id to distinguish asynchronous requests + * @param listener listener called with an array of calIWcapCalendar objects + * @return request object to track operation */ - void searchForCalendars( - in string searchString, - in unsigned long searchOptions, - in calIWcapSearchListener listener, - in unsigned long requestId ); - - /** - * Creates a new calendar for the session's user. - * - * @param calId calendar's calId (portion); - * without user's id, e.g. "test-cal". - * valid characters for the calId parameter are: - * - Alphabet characters (A-Z, a-z) - * - Numeric characters (0-9) - * - Three special characters - * - Dash (-) - * - Underscore (_) - * - Period (.) - * @param name calendar's name, e.g. "My Work Cal" - * @return created calendar - */ - calIWcapCalendar createCalendar( in string calId, in string name ); - - /** - * Deletes a calendar. Don't do any further calls on the deleted instance. - * - * @param cal calendar to be deleted - */ - void deleteCalendar( in calIWcapCalendar cal ); - - /** - * Subscribe to calendar(s). - * - * @param count length of cals - * @param calendars array of cals - */ - void subscribeToCalendars( - in unsigned long count, - [array, size_is(count)] in calIWcapCalendar cals ); - - /** - * Unsubscribe from calendar(s). - * - * @param count length of cals - * @param calendars array of cals - */ - void unsubscribeFromCalendars( - in unsigned long count, - [array, size_is(count)] in calIWcapCalendar cals ); - - - /** - * Gets the user's preferences. - * - * @param prefName preference name - * @return array of preference values - */ - void getUserPreferences( - in string prefName, - out unsigned long count, - [array, size_is(count), retval] out string properties ); - - /** - * Gets this user's default alarm start. - */ - readonly attribute calIDuration defaultAlarmStart; - - /** - * Gets this user's default alarm eMail addresses. - */ - void getDefaultAlarmEmails( - out unsigned long count, - [array, size_is(count), retval] out string emails ); - + calIWcapRequest searchForCalendars(in string searchString, + in unsigned long searchOptions, + in calIWcapRequestResultListener listener); /* xxx todo freebusy: separate into own interface? */ /** - * Gets free-busy entries for calid. + * Gets free-busy entries for calendar. * Results are notified to the passed listener instance. - * An error is notified to all registered calIObservers and - * to calIWcapFreeBusyListener::onGetFreeBusyTimes with rc != NS_OK. * - * @param calId a calid or "mailto:rfc822addr" - * @param dtRangeStart start time of free-busy search - * @param dtRangeEnd end time of free-busy search - * @param bBusyOnly whether to return busy entries only - * @param listener listener receiving results - * @param requestId request id to distinguish asynchronous requests + * @param calId calid or rfc822addr + * @param rangeStart start time of free-busy search + * @param rangeEnd end time of free-busy search + * @param bBusy whether to return busy entries or free entries + * @param listener called with an array of calIPeriod objects + * @return request object to track operation */ - void getFreeBusyTimes( - in string calId, - in calIDateTime dtRangeStart, - in calIDateTime dtRangeEnd, - in boolean bBusyOnly, - in calIWcapFreeBusyListener listener, - in boolean bAsync, - in unsigned long requestId ); + calIWcapRequest getFreeBusyTimes(in string calId, + in calIDateTime rangeStart, + in calIDateTime rangeEnd, + in boolean bBusy, + in calIWcapRequestResultListener listener); };