Bug 340949: no more event queue locking, minor fixes and cosmetics

git-svn-id: svn://10.0.0.236/trunk@215316 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
daniel.boelzle%sun.com 2006-11-15 17:01:43 +00:00
parent bf5bdadd37
commit a2836b3103
8 changed files with 464 additions and 308 deletions

View File

@ -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;
},

View File

@ -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()" );
};

View File

@ -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}")
},

View File

@ -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;

View File

@ -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;

View File

@ -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); } );
}
}

View File

@ -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;

View File

@ -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(