diff --git a/mozilla/calendar/providers/wcap/calWcapCalendar.js b/mozilla/calendar/providers/wcap/calWcapCalendar.js index 1adbf87fd71..6258d7b4bc4 100644 --- a/mozilla/calendar/providers/wcap/calWcapCalendar.js +++ b/mozilla/calendar/providers/wcap/calWcapCalendar.js @@ -57,6 +57,20 @@ function calWcapCalendar( calId, session ) { this.m_calId = calId; this.m_session = session; 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); } calWcapCalendar.prototype = { m_ifaces: [ Components.interfaces.calIWcapCalendar, @@ -237,7 +251,8 @@ calWcapCalendar.prototype = { this.m_bReadOnly || // xxx todo: check write permissions in advance // alarms only for own calendars: - !this.isOwnedCalendar); + // xxx todo: assume alarms if not logged in already + (this.session.isLoggedIn && !this.isOwnedCalendar)); }, set suppressAlarms( bSuppressAlarms ) { this.m_bSuppressAlarms = bSuppressAlarms; @@ -249,6 +264,14 @@ calWcapCalendar.prototype = { this.log("refresh()"); }, + getCommandUrl: + function( wcapCommand ) + { + var url = this.session.getCommandUrl(wcapCommand); + url += ("&calid=" + encodeURIComponent(this.calId)); + return url; + }, + // calIWcapCalendar: m_session: null, @@ -261,14 +284,16 @@ calWcapCalendar.prototype = { // like a subscribed one... m_calId: null, get calId() { - return this.m_calId || this.session.defaultCalId; + if (this.m_calId) + return this.m_calId; + this.session.assureLoggedIn(); + return this.session.defaultCalId; }, set calId( id ) { this.log( "setting calId to " + id ); this.m_calId = id; // refresh calprops: - this.m_calProps = null; - this.getCalProps_( true /* async */ ); + this.getCalProps_( true /*async*/, true /*refresh*/ ); }, get ownerId() { @@ -328,12 +353,12 @@ calWcapCalendar.prototype = { }, m_calProps: null, getCalProps_: - function( bAsync ) + function( bAsync, bRefresh ) { + this.session.assureLoggedIn(); try { - if (this.m_calProps == null) { - var url = this.session.getCommandUrl( "get_calprops" ); - url += ("&calid=" + encodeURIComponent(this.calId)); + if (bRefresh || !this.m_calProps) { + var url = this.getCommandUrl( "get_calprops" ); url += "&fmt-out=text%2Fxml"; var this_ = this; function resp( wcapResponse ) { @@ -366,7 +391,8 @@ calWcapCalendar.prototype = { // readOnly attribute, thus we would run into endless recursion here this.m_bReadOnly = true; this.logError( exc ); - throw exc; + if (!bAsync) + throw exc; } return this.m_calProps; }, diff --git a/mozilla/calendar/providers/wcap/calWcapCalendarItems.js b/mozilla/calendar/providers/wcap/calWcapCalendarItems.js index 4304f3d8ab4..8995b03edfe 100644 --- a/mozilla/calendar/providers/wcap/calWcapCalendarItems.js +++ b/mozilla/calendar/providers/wcap/calWcapCalendarItems.js @@ -61,7 +61,7 @@ calWcapCalendar.prototype.encodeAttendee = function( ret += ("^ROLE=" + attendee.role); if (attendee.commonName != null) ret += ("^CN=" + encodeURIComponent(attendee.commonName)); - ret += ("^" + attendee.id); + ret += ("^" + encodeURIComponent(attendee.id)); return ret; }; @@ -196,12 +196,10 @@ calWcapCalendar.prototype.storeItem = function( item, oldItem, receiverFunc ) var bIsEvent = isEvent(item); var bIsParent = isParent(item); - var url = this.session.getCommandUrl( bIsEvent ? "storeevents" - : "storetodos" ); - url += ("&calid=" + encodeURIComponent(this.calId)); + var url = this.getCommandUrl( bIsEvent ? "storeevents" : "storetodos" ); if (oldItem) { // modifying - url += ("&uid=" + item.id); + url += ("&uid=" + encodeURIComponent(item.id)); if (bIsParent) { // (WCAP_STORE_TYPE_MODIFY) error if not existing: url += "&storetype=2"; @@ -359,7 +357,7 @@ calWcapCalendar.prototype.storeItem = function( item, oldItem, receiverFunc ) 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); + this.log("dtstart=" + dtstart + "\ndtend=" + dtend, item.id); bIsAllDay = (dtstart.isDate && dtend.isDate); } else { // calITodo: @@ -581,7 +579,7 @@ calWcapCalendar.prototype.adoptItem_resp = function( } }; -calWcapCalendar.prototype.adoptItem = function( item, listener ) +calWcapCalendar.prototype.adoptItem_queued = function( item, listener ) { this.log( "adoptItem() call: " + item.title ); try { @@ -659,7 +657,8 @@ calWcapCalendar.prototype.modifyItem_resp = function( } }; -calWcapCalendar.prototype.modifyItem = function( newItem, oldItem, listener ) +calWcapCalendar.prototype.modifyItem_queued = function( + newItem, oldItem, listener ) { this.log( "modifyItem() call: " + newItem.id ); try { @@ -719,7 +718,7 @@ calWcapCalendar.prototype.deleteItem_resp = function( } }; -calWcapCalendar.prototype.deleteItem = function( item, listener ) +calWcapCalendar.prototype.deleteItem_queued = function( item, listener ) { this.log( "deleteItem() call: " + item.id ); try { @@ -727,10 +726,9 @@ calWcapCalendar.prototype.deleteItem = function( item, listener ) if (item.id == null) throw new Components.Exception("no item id!"); - var url = this.session.getCommandUrl( + var url = this.getCommandUrl( isEvent(item) ? "deleteevents_by_id" : "deletetodos_by_id" ); - url += ("&calid=" + encodeURIComponent(this.calId)); - url += ("&uid=" + item.id); + url += ("&uid=" + encodeURIComponent(item.id)); if (isParent(item)) // delete THIS AND ALL: url += "&mod=4&rid=0"; @@ -760,6 +758,20 @@ calWcapCalendar.prototype.parseItems = function( 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 = []; @@ -845,36 +857,14 @@ calWcapCalendar.prototype.parseItems = function( var ar = contactsProp.value.split(":"); if (ar.length > 1) { var lastAck = ar[1]; - if (lastAck.length > 0) { - var dtLastAck = new CalDateTime(); - dtLastAck.icalString = lastAck; // TZID is UTC - dtLastAck.normalize(); - // shift to alarm comp: - item.alarmLastAck = dtLastAck; + if (lastAck.length > 0) { // shift to alarm comp: + item.alarmLastAck = getDatetimeFromIcalString( + lastAck); // TZID is UTC } } } - item.calendar = this_.superCalendar; - var rid = subComp.getFirstProperty("RECURRENCE-ID"); - if (rid == null) { - unexpandedItems.push( item ); - if (item.recurrenceInfo != null) - uid2parent[item.id] = item; - } - else { - // xxx todo: IMO ought not be needed here: TZID is UTC - var dtrid = getDatetimeFromIcalProp(rid); - if (LOG_LEVEL > 1) { - this_.log( "exception item: rid=" + dtrid.icalString ); - } - item.recurrenceId = dtrid; - // force no recurrence info: - item.recurrenceInfo = null; - excItems.push( item ); - } - - if (item.title == null) { + if (!item.title) { // assumed to look at a subscribed calendar, // so patch title for private items: switch (item.privacy) { @@ -886,65 +876,105 @@ calWcapCalendar.prototype.parseItems = function( break; } } + + item.calendar = this_.superCalendar; + var rid = item.recurrenceId; + if (rid) { + if (LOG_LEVEL > 1) { + this_.log( "exception item: " + item.title + + "\nrid=" + rid.icalString, + "item.id=" + item.id ); + } + excItems.push( item ); + } + else if (item.recurrenceInfo) { + unexpandedItems.push( item ); + uid2parent[item.id] = item; + } + else if (maxResult == 0 || nItems < maxResult) { + if (LOG_LEVEL > 2) { + this_.log( "item: " + item.title + "\n" + + item.icalString ); + } + if (!bLeaveMutable) + item.makeImmutable(); + receiverFunc( [item] ); + } } }, maxResult ); - + // tag "exceptions", i.e. items with rid: for each ( var item in excItems ) { var parent = uid2parent[item.id]; - if (parent == null) { - this.logError( "parseItems(): no parent item for rid=" + - item.recurrenceId ); - } - else { + if (parent) { item.parentItem = parent; item.makeImmutable(); 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 (!bLeaveMutable) + item.makeImmutable(); + receiverFunc( [item] ); + } } - var items = []; - for ( var i = 0; - (i < unexpandedItems.length) && - (maxResult == 0 || items.length < maxResult); - ++i ) + if (itemFilter & Components.interfaces.calICalendar + .ITEM_FILTER_CLASS_OCCURRENCES) { - var item = unexpandedItems[i]; - if (!bLeaveMutable) - item.makeImmutable(); - if (item.recurrenceInfo != null && - (itemFilter & Components.interfaces.calICalendar - .ITEM_FILTER_CLASS_OCCURRENCES)) - { + for each ( var item in unexpandedItems ) { + if (maxResult != 0 && nItems >= maxResult) + break; + if (!bLeaveMutable) + item.makeImmutable(); var occurrences = item.recurrenceInfo.getOccurrences( rangeStart, rangeEnd, - maxResult == 0 ? 0 : maxResult - items.length, + maxResult == 0 ? 0 : maxResult - nItems, {} ); if (LOG_LEVEL > 1) { this.log( "item: " + item.title + " has " + occurrences.length.toString() + " occurrences." ); + if (LOG_LEVEL > 2) { + for each ( var occ in occurrences ) { + this.log("item: " + occ.title + "\n" + occ.icalString); + } + } } // only proxies returned: - items = items.concat( occurrences ); - } - else { - items.push( item ); + receiverFunc( occurrences ); + nItems += occurrences.length; } } - - if (LOG_LEVEL > 1) { - this.log( "parseItems(): returned " + items.length + " items" ); + else { + if (maxResult != 0 && + (nItems + unexpandedItems.length) > maxResult) { + unexpandedItems.length = (maxResult - nItems); + } + if (!bLeaveMutable) { + for each ( var item in unexpandedItems ) { + item.makeImmutable(); + } + } if (LOG_LEVEL > 2) { - for each ( var item in items ) { + for each ( var item in unexpandedItems ) { this.log( "item: " + item.title + "\n" + item.icalString ); } } + receiverFunc( unexpandedItems ); + nItems += unexpandedItems.length; + } + + if (LOG_LEVEL > 1) { + this.log( "parseItems_(): notified " + nItems + " items" ); } - return items; }; -calWcapCalendar.prototype.getItem = function( id, listener ) +calWcapCalendar.prototype.getItem_queued = function( id, listener ) { // xxx todo: test // xxx todo: howto detect whether to call @@ -983,18 +1013,17 @@ calWcapCalendar.prototype.getItem = function( id, listener ) var params = ("&relativealarm=1&compressed=1&recurring=1" + "&fmt-out=text%2Fcalendar"); - params += ("&calid=" + encodeURIComponent(this.calId)); - params += ("&uid=" + id); + params += ("&uid=" + encodeURIComponent(id)); try { // most common: event this.session.issueSyncRequest( - this.session.getCommandUrl( "fetchevents_by_id" ) + params, + this.getCommandUrl( "fetchevents_by_id" ) + params, stringToIcal, syncResponseFunc ); } catch (exc) { // try again, may be a task: this.session.issueSyncRequest( - this.session.getCommandUrl( "fetchtodos_by_id" ) + params, + this.getCommandUrl( "fetchtodos_by_id" ) + params, stringToIcal, syncResponseFunc ); } } @@ -1077,28 +1106,36 @@ calWcapCalendar.prototype.getItems_resp = function( }; this.session.getFreeBusyTimes( this.calId, rangeStart, rangeEnd, true /*bBusyOnly*/, - freeBusyListener, - true /*bAsync*/, 0 /*requestId*/ ); + freeBusyListener, true/*async*/, 0 /*requestId*/ ); } return; } var icalRootComp = wcapResponse.data; // first statement, may throw - var items = this.parseItems( - icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd ); - - if (listener != null) { - listener.onGetResult( this.superCalendar, Components.results.NS_OK, - Components.interfaces.calIItemBase, - this.log( "getItems(): success." ), - items.length, items ); + 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, - items.length == 1 ? items[0].id : null, null ); - this.log( items.length.toString() + " items delivered." ); + null, null ); } + else { + // just to check returned data: + this.parseItems( + icalRootComp, itemFilter, maxResult, rangeStart, rangeEnd ); + } + this.log( "getItems(): success." ); } catch (exc) { if (listener != null) { @@ -1143,7 +1180,7 @@ function getItemFilterUrlPortions( itemFilter ) return url; } -calWcapCalendar.prototype.getItems = function( +calWcapCalendar.prototype.getItems_queued = function( itemFilter, maxResult, rangeStart, rangeEnd, listener ) { // assure DATETIMEs: @@ -1162,17 +1199,15 @@ calWcapCalendar.prototype.getItems = function( ",\n\trangeStart=" + zRangeStart + ",\n\trangeEnd=" + zRangeEnd ); try { - var url = this.session.getCommandUrl( "fetchcomponents_by_range" ); + var url = this.getCommandUrl( "fetchcomponents_by_range" ); url += ("&relativealarm=1&compressed=1&recurring=1" + - "&fmt-out=text%2Fcalendar&calid=" + - encodeURIComponent(this.calId)); + "&fmt-out=text%2Fcalendar"); // setting component-type, compstate filters: url += getItemFilterUrlPortions(itemFilter); if (maxResult > 0) url += ("&maxResult=" + maxResult); - // xxx todo: correctly normalized dates to zulu time? url += ("&dtstart=" + zRangeStart); url += ("&dtend=" + zRangeEnd); @@ -1203,6 +1238,7 @@ calWcapCalendar.prototype.getItems = function( this.log( "getItems() returning." ); }; + function SyncState( finishFunc, abortFunc ) { this.m_finishFunc = finishFunc; this.m_abortFunc = abortFunc; @@ -1214,9 +1250,7 @@ SyncState.prototype = { m_exc: null, acquire: function() { /*this.checkAborted();*/ ++this.m_state; }, - release: - function() - { + release: function() { /*this.checkAborted();*/ --this.m_state; // logMessage( "sync-state", "m_state = " + this.m_state ); @@ -1229,9 +1263,9 @@ SyncState.prototype = { if (this.m_exc) throw this.m_exc; }, - get hasAborted() { return this.m_exc != null; }, + get isAborted() { return this.m_exc != null; }, abort: function( exc ) { - if (!this.hasAborted) // store only first error that has occurred + if (!this.isAborted) // store only first error that has occurred this.m_exc = exc; this.m_abortFunc( exc ); } @@ -1273,14 +1307,11 @@ calWcapCalendar.prototype.syncChangesTo_resp = function( { try { var icalRootComp = wcapResponse.data; // first statement, may throw - var items = this.parseItems( + var items = this.parseItems_( + function(items) { items.forEach(func) }, icalRootComp, Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS, 0, null, null ); - for each ( var item in items ) { - // xxx todo: ignore single errors and continue? - func( item ); - } } catch (exc) { syncState.abort( exc ); @@ -1288,11 +1319,10 @@ calWcapCalendar.prototype.syncChangesTo_resp = function( syncState.release(); }; -calWcapCalendar.prototype.syncChangesTo = function( +calWcapCalendar.prototype.syncChangesTo_queued = function( destCal, itemFilter, dtFrom_, listener ) { var dtFrom = dtFrom_; -// this.ensureOnline(); if (dtFrom) { dtFrom = dtFrom.clone(); // assure DATETIMEs: @@ -1322,8 +1352,8 @@ calWcapCalendar.prototype.syncChangesTo = function( var syncState = new SyncState( // finishFunc: function() { - if (listener != null) { - if (syncState.hasAborted) { + if (listener) { + if (syncState.isAborted) { this_.log( "firing SYNC failed!" ); listener.onOperationComplete( this_.superCalendar, @@ -1340,7 +1370,7 @@ calWcapCalendar.prototype.syncChangesTo = function( }, // abortFunc: function( exc ) { - if (listener != null) { + if (listener) { listener.onOperationComplete( this_.superCalendar, Components.results.NS_ERROR_FAILURE, @@ -1353,17 +1383,17 @@ calWcapCalendar.prototype.syncChangesTo = function( Components.interfaces.calIOperationListener.ADD, syncState ); var modifiedItems = []; - this.log( "syncChangesTo(): getting last modifications..." ); + this.log( "getting last modifications...", "syncChangesTo()" ); var modifyItemListener = new FinishListener( Components.interfaces.calIOperationListener.MODIFY, syncState ); - var params = ("&relativealarm=1&compressed=1&recurring=1&calid=" + - encodeURIComponent(this.calId)); - params += ("&fmt-out=text%2Fcalendar&dtstart=" + zdtFrom); - params += ("&dtend=" + getIcalUTC( this.session.getServerTime(now) )); + var params = ("&relativealarm=1&compressed=1&recurring=1" + + "&fmt-out=text%2Fcalendar"); + params += ("&dtstart=" + zdtFrom); + params += ("&dtend=" + getIcalUTC(this.session.getServerTime(now))); syncState.acquire(); this.session.issueAsyncRequest( - this.session.getCommandUrl("fetchcomponents_by_lastmod") + + this.getCommandUrl("fetchcomponents_by_lastmod") + params + getItemFilterUrlPortions(itemFilter), stringToIcal, function( wcapResponse ) { @@ -1377,7 +1407,8 @@ calWcapCalendar.prototype.syncChangesTo = function( if (bAdd) { // xxx todo: verify whether exceptions // have been written - this_.log( "new item: " + item.id ); + this_.log( "new item: " + item.id, + "syncChangesTo_resp()" ); if (destCal) { syncState.acquire(); destCal.addItem( item, addItemListener ); @@ -1386,7 +1417,8 @@ calWcapCalendar.prototype.syncChangesTo = function( calObserver.onAddItem( item ); } else { - this_.log( "modified item: " + item.id ); + this_.log( "modified item: " + item.id, + "syncChangesTo_resp()" ); if (destCal) { syncState.acquire(); destCal.modifyItem( item, null, @@ -1398,12 +1430,12 @@ calWcapCalendar.prototype.syncChangesTo = function( } ); } ); - this.log( "syncChangesTo(): getting deleted items..." ); + this.log( "getting deleted items...", "syncChangesTo()" ); var deleteItemListener = new FinishListener( Components.interfaces.calIOperationListener.DELETE, syncState ); syncState.acquire(); this.session.issueAsyncRequest( - this.session.getCommandUrl("fetch_deletedcomponents") + params + + this.getCommandUrl("fetch_deletedcomponents") + params + getItemFilterUrlPortions( itemFilter & // only component-type Components.interfaces.calICalendar .ITEM_FILTER_TYPE_ALL ), @@ -1414,14 +1446,15 @@ calWcapCalendar.prototype.syncChangesTo = function( function( item ) { // don't delete anything that has been touched // by lastmods: - for each ( var mid in modifiedItems ) { - if (item.id == mid) { - this_.log( "skipping deletion of " + item.id ); - return; - } + 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 ); + this_.log( "deleted item: " + item.id, + "syncChangesTo_resp()" ); if (destCal) { syncState.acquire(); destCal.deleteItem( @@ -1436,7 +1469,8 @@ calWcapCalendar.prototype.syncChangesTo = function( var parent = item.parentItem.clone(); parent.recurrenceInfo.removeOccurrenceAt( item.recurrenceId ); - this_.log( "modified parent: " + parent.id ); + this_.log( "modified parent: " + parent.id, + "syncChangesTo_resp()" ); if (destCal) { syncState.acquire(); destCal.modifyItem( parent, item, @@ -1459,7 +1493,7 @@ calWcapCalendar.prototype.syncChangesTo = function( SYNC, null, dtFrom_ /* pass original stamp: => empty sync range */ ); } - this.logError( "syncChangesTo() ignored: " + errorToString(exc) ); + this.logError("ignored: " + errorToString(exc), "syncChangesTo()"); } else { if (listener) { @@ -1470,6 +1504,6 @@ calWcapCalendar.prototype.syncChangesTo = function( this.notifyError( exc ); } } - this.log( "syncChangesTo() returning." ); + this.log( "finished.", "syncChangesTo()" ); }; diff --git a/mozilla/calendar/providers/wcap/calWcapCalendarModule.js b/mozilla/calendar/providers/wcap/calWcapCalendarModule.js index 4b83e951941..09bdd3ca75b 100644 --- a/mozilla/calendar/providers/wcap/calWcapCalendarModule.js +++ b/mozilla/calendar/providers/wcap/calWcapCalendarModule.js @@ -57,6 +57,7 @@ var CalTodo; var CalDateTime; var CalDuration; var XmlHttpRequest; +var Timer; // some string resources: var g_privateItemTitle; @@ -70,9 +71,6 @@ var CACHE = "off"; // denotes where to host local storage calendar(s) var CACHE_DIR = null; -// timeout for sync network requests (in secs): -var SYNC_REQUESTS_TIMEOUT = 10; - // logging: #expand var LOG_LEVEL = __LOG_LEVEL__; var LOG_TIMEZONE = null; @@ -95,6 +93,8 @@ function initWcapProvider() "@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" ); // some string resources: g_privateItemTitle = getWcapBundle().GetStringFromName( @@ -105,9 +105,6 @@ function initWcapProvider() "busyItem.title.text"); g_busyPhantomItemUuidPrefix = ("PHANTOM_uuid" + getTime().icalString); - SYNC_REQUESTS_TIMEOUT = getPref( - "calendar.wcap.sync_request_timeout", 10); - LOG_TIMEZONE = getPref("calendar.timezone.local", null); var logLevel = getPref("calendar.wcap.log_level", null); @@ -192,7 +189,7 @@ var calWcapCalendarModule = { // nsIModule: WcapSessionInfo: { classDescription: "Sun Java System Calendar Server WCAP Session", - contractID: "@mozilla.org/calendar/session;1?type=wcap", + contractID: "@mozilla.org/calendar/wcap/session;1", classID: Components.ID("{CBF803FD-4469-4999-AE39-367AF1C7B077}") }, diff --git a/mozilla/calendar/providers/wcap/calWcapRequest.js b/mozilla/calendar/providers/wcap/calWcapRequest.js index deefef40747..8b89cbd96d5 100644 --- a/mozilla/calendar/providers/wcap/calWcapRequest.js +++ b/mozilla/calendar/providers/wcap/calWcapRequest.js @@ -175,11 +175,19 @@ function streamToString( inStream, charset ) var convStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] .createInstance(Components.interfaces.nsIConverterInputStream); - convStream.init( inStream, charset, 0, 0x0000 ); - var str = ""; - var str_ = {}; - while (convStream.readString( -1, str_ )) { - str += str_.value; + try { + convStream.init( inStream, charset, 0, 0x0000 ); + 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; } @@ -192,24 +200,12 @@ function issueSyncRequest( url, receiverFunc, bLogging ) logMessage( "issueSyncRequest( \"" + url + "\" )", "opening channel." ); } + var channel = getIoService().newChannel( url, "" /* charset */, null /* baseURI */ ); channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - var timer = Components.classes["@mozilla.org/timer;1"] - .createInstance(Components.interfaces.nsITimer); - timer.initWithCallback( - { // nsITimerCallback: - notify: function( timer_ ) { - if (channel.isPending()) - channel.cancel(NS_BINDING_FAILED); - } - }, - SYNC_REQUESTS_TIMEOUT * 1000, - Components.interfaces.nsITimer.TYPE_ONE_SHOT ); - var stream = channel.open(); - timer.cancel(); - + var stream = channel.open(); var status = channel.status; if (status == Components.results.NS_OK) { var charset = channel.contentCharset; diff --git a/mozilla/calendar/providers/wcap/calWcapSession.js b/mozilla/calendar/providers/wcap/calWcapSession.js index 82c5d13219a..f2056fedc65 100644 --- a/mozilla/calendar/providers/wcap/calWcapSession.js +++ b/mozilla/calendar/providers/wcap/calWcapSession.js @@ -45,6 +45,13 @@ function calWcapSession() { this.wrappedJSObject = this; this.m_observers = []; this.m_calIdToCalendar = {}; + this.m_asyncQueue = new AsyncQueue(); + this.m_calPropsTimer = new Timer(); + + // init queued calls: + this.getFreeBusyTimes = makeQueuedCall(this.asyncQueue, + this, this.getFreeBusyTimes_queued); + // listen for shutdown, being logged out: // network:offline-about-to-go-offline will be fired for // XPCOM shutdown, too. @@ -57,7 +64,11 @@ function calWcapSession() { observerService.addObserver( this, "network:offline-about-to-go-offline", false /* don't hold weakly: xxx todo */ ); } + calWcapSession.prototype = { + m_asyncQueue: null, + get asyncQueue() { return this.m_asyncQueue; }, + m_ifaces: [ Components.interfaces.calIWcapSession, Components.interfaces.nsIInterfaceRequestor, Components.interfaces.nsIClassInfo, @@ -270,6 +281,7 @@ calWcapSession.prototype = { m_sessionId: null, m_bNoLoginsAnymore: false, + m_calPropsTimer: null, getSessionId: function( timedOutSessionId ) @@ -290,74 +302,83 @@ calWcapSession.prototype = { if (!this.m_sessionId || this.m_sessionId == timedOutSessionId) { - var this_ = this; - lockedExec( - function() { - if (!this_.m_bNoLoginsAnymore && - (!this_.m_sessionId || - this_.m_sessionId == timedOutSessionId)) { - - try { - this_.m_sessionId = null; - if (timedOutSessionId != null) { - this_.log( "session timeout; " + - "prompting to reconnect." ); - 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 ); - } - } - - var sessionUri = this_.uri.clone(); - sessionUri.userPass = ""; - if (sessionUri.scheme.toLowerCase() != "https" && - sessionUri.port == -1 /* no port specified */) - { - // silently probe for https support: - try { // enforce https: - sessionUri.scheme = "https"; - this_.getSessionId_(sessionUri); - } - 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." ); - throw exc; - } - - this_.getServerTimeDiff(true /* refresh */); - this_.getSupportedTimezones(true /* refresh */); - // preread calprops for subscribed calendars: - var cals = this_.getSubscribedCalendars({}); - for each ( cal in cals ) { - cal.getCalProps_(true /* async */); - } + try { + this.m_sessionId = null; + if (timedOutSessionId) { + this.log( "session timeout; prompting to reconnect." ); + 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 ); } - } ); + } + + var sessionUri = this.uri.clone(); + sessionUri.userPass = ""; + if (sessionUri.scheme.toLowerCase() != "https" && + sessionUri.port == -1 /* no port specified */) + { + // silently probe for https support: + try { // enforce https: + sessionUri.scheme = "https"; + this.getSessionId_(sessionUri); + } + 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." ); + throw exc; + } + + this.getServerTimeDiff(true /* refresh */); + + if (!timedOutSessionId) // timezones don't change that frequently + this.getSupportedTimezones(true /* refresh */); + + // invalidate calprops of all calendars: + var cals = this.getSubscribedCalendars({}); + for each ( cal in cals ) { + cal.m_calProps = null; + } + + 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 > 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) { @@ -518,7 +539,7 @@ calWcapSession.prototype = { this.m_sessionId = prop.value; this.m_userId = user; this.m_sessionUri = sessionUri; - this.log( "WCAP login succeeded, setting sessionUri to " + + this.log( "login succeeded, setting sessionUri to " + this.sessionUri.spec ); return true; }, @@ -538,7 +559,7 @@ calWcapSession.prototype = { icalRootComp = getIcsService().parseICS( str ); } catch (exc) { - this.log( exc ); // soft error; request denied etc. + this.log( errorToString(exc) ); // soft error; request denied etc. throw new Components.Exception( getWcapBundle().formatStringFromName( "accessingServerFailedError.text", [uri.hostPort], 1 ), @@ -689,6 +710,16 @@ calWcapSession.prototype = { return ((list && list.length > 0) ? list[0] : this.userId); }, + assureLoggedIn: + function() + { + if (!this.isLoggedIn) { + throw new Components.Exception( + "Not logged in yet.", + Components.results.NS_ERROR_NOT_AVAILABLE); + } + }, + get isLoggedIn() { return this.m_sessionId != null; }, login: @@ -712,10 +743,10 @@ calWcapSession.prototype = { try { checkWcapXmlErrno( issueSyncXMLRequest(url), -1 /* logout successfull */ ); - this.log( "WCAP logout succeeded." ); + this.log( "logout succeeded." ); } catch (exc) { - this.log( "WCAP logout failed: " + exc ); + this.log( "logout failed: " + exc ); Components.utils.reportError( exc ); } this.m_sessionId = null; @@ -802,6 +833,7 @@ calWcapSession.prototype = { function( calId, name, bAllowDoubleBooking, bSetCalProps, bAddToSubscribed ) { try { + this.assureLoggedIn(); var url = this.getCommandUrl( "createcalendar" ); url += ("&allowdoublebook=" + (bAllowDoubleBooking ? "1" : "0")); url += ("&set_calprops=" + (bSetCalProps ? "1" : "0")); @@ -824,6 +856,7 @@ calWcapSession.prototype = { function( calId, bRemoveFromSubscribed ) { try { + this.assureLoggedIn(); var url = this.getCommandUrl( "deletecalendar" ); url += ("&unsubscribe=" + (bRemoveFromSubscribed ? "1" : "0")); url += ("&calid=" + encodeURIComponent(calId)); @@ -841,6 +874,7 @@ calWcapSession.prototype = { function( calIds, bSubscribe ) { try { + this.assureLoggedIn(); var url = this.getCommandUrl( bSubscribe ? "subscribe_calendars" : "unsubscribe_calendars" ); var calId = ""; @@ -849,7 +883,7 @@ calWcapSession.prototype = { calId += ";"; calId += encodeURIComponent(calIds[i]); } - url += ("&calid=" + calId); + url += ("&calid=" + encodeURIComponent(calId)); this.issueSyncRequest( url + "&fmt-out=text%2Fxml", stringToXml ); this.m_userPrefs = null; // reread prefs } @@ -877,6 +911,7 @@ calWcapSession.prototype = { { try { if (this.m_userPrefs == null) { + this.assureLoggedIn(); var url = this.getCommandUrl( "get_userprefs" ); url += ("&userid=" + encodeURIComponent(this.userId)); this.m_userPrefs = this.issueSyncRequest( @@ -908,10 +943,8 @@ calWcapSession.prototype = { var item = nodeList.item(i); var str = item.textContent; var slash = str.indexOf( '/' ); - var start = new CalDateTime(); - start.icalString = str.substr( 0, slash ); - var end = new CalDateTime(); - end.icalString = str.substr( slash + 1 ); + var start = getDatetimeFromIcalString(str.substr(0, slash)); + var end = getDatetimeFromIcalString(str.substr(slash + 1)); var entry = { isBusyEntry: (item.attributes.getNamedItem("FBTYPE").nodeValue @@ -926,8 +959,9 @@ calWcapSession.prototype = { requestId, calId, ret.length, ret ); } if (LOG_LEVEL > 0) { - this.log( "getFreeBusyTimes_resp() calId=" + calId + ", " + - getWcapRequestStatusString(xml) ); + this.log( "calId=" + calId + ", " + + getWcapRequestStatusString(xml), + "getFreeBusyTimes_resp()" ); } } catch (exc) { @@ -948,9 +982,8 @@ calWcapSession.prototype = { } }, - getFreeBusyTimes: - function( calId, rangeStart, rangeEnd, bBusyOnly, listener, - bAsync, requestId ) + getFreeBusyTimes_queued: + function( calId, rangeStart, rangeEnd, bBusyOnly, listener, b, requestId ) { try { // assure DATETIMEs: @@ -964,9 +997,11 @@ calWcapSession.prototype = { } var zRangeStart = getIcalUTC(rangeStart); var zRangeEnd = getIcalUTC(rangeEnd); - this.log( "getFreeBusyTimes():\n\trangeStart=" + zRangeStart + - ",\n\trangeEnd=" + zRangeEnd ); + this.log( "\n\trangeStart=" + zRangeStart + + ",\n\trangeEnd=" + zRangeEnd, + "getFreeBusyTimes()" ); + this.assureLoggedIn(); var url = this.getCommandUrl( "get_freebusy" ); url += ("&calid=" + encodeURIComponent(calId)); url += ("&busyonly=" + (bBusyOnly ? "1" : "0")); @@ -979,18 +1014,14 @@ calWcapSession.prototype = { this_.getFreeBusyTimes_resp( wcapResponse, calId, listener, requestId ); } - if (bAsync) - this.issueAsyncRequest( url, stringToXml, resp ); - else - this.issueSyncRequest( url, stringToXml, resp ); + this.issueAsyncRequest( url, stringToXml, resp ); } catch (exc) { this.notifyError( exc ); if (listener != null) listener.onGetFreeBusyTimes( exc, requestId, calId, 0, [] ); - throw exc; } - } + }, }; var g_confirmedHttpLogins = null; diff --git a/mozilla/calendar/providers/wcap/calWcapUtils.js b/mozilla/calendar/providers/wcap/calWcapUtils.js index 20df96c58d3..82c3fd2b9c6 100644 --- a/mozilla/calendar/providers/wcap/calWcapUtils.js +++ b/mozilla/calendar/providers/wcap/calWcapUtils.js @@ -121,54 +121,6 @@ function getCalendarManager() return g_calendarManager; }; -var g_bInitedLockedExec = false; -var g_eventQueueService = null; -var g_threadManager = null; - -/** Locks event dispatching for the current event queue while the passed - function is executed. - - xxx todo: this hinders timeeout events for sync requests - to be dispatched! -*/ -function lockedExec( func ) -{ - if (!g_bInitedLockedExec) { - try { - var cl = Components.classes["@mozilla.org/event-queue-service;1"]; - if (cl) { - g_eventQueueService = - cl.getService(Components.interfaces.nsIEventQueueService); - } - } - catch (exc) { // eventQueue has vanished on trunk - } - if (!g_eventQueueService) { - g_threadManager = - Components.classes["@mozilla.org/thread-manager;1"] - .getService(Components.interfaces.nsIThreadManager); - } - g_bInitedLockedExec = true; - } - - var queue; - var ret; - if (g_eventQueueService) - queue = g_eventQueueService.pushThreadEventQueue(); - else // we are on trunk using nsIThreadInternal - g_threadManager.currentThread.pushEventQueue(null); - try { - ret = func(); - } - finally { - if (g_eventQueueService) - g_eventQueueService.popThreadEventQueue(queue); - else // we are on trunk using nsIThreadInternal - g_threadManager.currentThread.popEventQueue(); - } - return ret; -} - var g_wcapBundle = null; function getWcapBundle() { @@ -204,10 +156,6 @@ function isParent( item ) throw new Components.Exception( "proxy has different id than its parent!"); } - if (item.parentItem.recurrenceId) { - throw new Components.Exception("parent has recurrenceId: " + - item.parentItem.recurrenceId); - } return (!item.recurrenceId); } @@ -259,20 +207,25 @@ function getIcalUTC( dt ) } } -function getDatetimeFromIcalProp( prop ) +function getDatetimeFromIcalString( val ) { - if (!prop) - return null; - var val = prop.valueAsIcalString; - if (val.length == 0 || val == "0") + if (!val || val.length == 0 || val == "0") return null; // assuming timezone is known: var dt = new CalDateTime(); dt.icalString = val; -// dt.makeImmutable(); +// if (dt.icalString != val) +// logMessage("date-time error", dt.icalString + " vs. " + val); return dt; } +function getDatetimeFromIcalProp( prop ) +{ + if (!prop) + return null; + return getDatetimeFromIcalString(prop.valueAsIcalString); +} + function getPref(prefName, defaultValue) { const nsIPrefBranch = Components.interfaces.nsIPrefBranch; @@ -316,3 +269,124 @@ function setPref(prefName, value) } } +function AsyncQueue() +{ + this.wrappedJSObject = this; + this.m_queue = []; +} +AsyncQueue.prototype = { + m_queue: null, + + 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.nsISecurityCheckedComponent, + 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, + + // nsISecurityCheckedComponent; + canCreateWrapper: function(iid) { return "AllAccess"; }, + canCallMethod: function(iid, methodName) { return "AllAccess"; }, + canGetProperty: function(iid, propertyName) { return "AllAccess"; }, + canSetProperty: function(iid, propertyName) { return "AllAccess"; }, + + // 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/mozilla/calendar/providers/wcap/public/calIWcapCalendar.idl b/mozilla/calendar/providers/wcap/public/calIWcapCalendar.idl index 2ca8b3ada14..39a3aeb8311 100644 --- a/mozilla/calendar/providers/wcap/public/calIWcapCalendar.idl +++ b/mozilla/calendar/providers/wcap/public/calIWcapCalendar.idl @@ -53,6 +53,7 @@ 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 */ readonly attribute string calId; diff --git a/mozilla/calendar/providers/wcap/public/calIWcapSession.idl b/mozilla/calendar/providers/wcap/public/calIWcapSession.idl index 9ccb695b6e1..8ebc5e45db0 100755 --- a/mozilla/calendar/providers/wcap/public/calIWcapSession.idl +++ b/mozilla/calendar/providers/wcap/public/calIWcapSession.idl @@ -196,18 +196,15 @@ interface calIWcapSession : nsISupports /* xxx todo freebusy: separate into own interface? */ /** * Gets free-busy entries for calid. - * Results are notifies to passed listener instance. + * Results are notified to passed listener instance. * An error is notified to all registered calIObservers and * to calIWcapFreeBusyListener::onGetFreeBusyTimes with rc != NS_OK. - * Additionally, when an error occurs within getFreeBusyTimes, - * the error is also thrown. * * @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 bAsync whether the listener receives results asynchronously * @param requestId request id to distinguish asynchronous requests */ void getFreeBusyTimes(