bug 379198 Make Lightning understand event updates r=berend
git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@246105 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -48,6 +48,7 @@ const CAL_ITIP_PROC_DELETE_OP = 3;
|
||||
*/
|
||||
function calItipProcessor() {
|
||||
this.wrappedJSObject = this;
|
||||
this._handledID = null;
|
||||
}
|
||||
|
||||
calItipProcessor.prototype = {
|
||||
@@ -115,11 +116,6 @@ calItipProcessor.prototype = {
|
||||
var autoResponse = respItipItem.autoResponse;
|
||||
var targetCalendar = respItipItem.targetCalendar;
|
||||
|
||||
// XXX Support for transports other than email go here.
|
||||
// For now we just assume it's email.
|
||||
var transport = Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"].
|
||||
createInstance(Components.interfaces.calIItipTransport);
|
||||
|
||||
// Sanity checks using the first item
|
||||
var itemList = respItipItem.getItemList({ });
|
||||
var calItem = itemList[0];
|
||||
@@ -141,44 +137,63 @@ calItipProcessor.prototype = {
|
||||
"response method: " + respMethod);
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
// Check to see if we have an existing item or not, then continue
|
||||
// processing appropriately
|
||||
var i =0;
|
||||
while (calItem) {
|
||||
switch (recvMethod) {
|
||||
case "REQUEST":
|
||||
// Only add to calendar if we accepted invite
|
||||
var replyStat = this._getReplyStatus(calItem,
|
||||
transport.defaultIdentity);
|
||||
if (replyStat == "DECLINED") {
|
||||
break;
|
||||
}
|
||||
// else fall through
|
||||
case "PUBLISH":
|
||||
if (!this._processCalendarAction(calItem,
|
||||
CAL_ITIP_PROC_ADD_OP,
|
||||
targetCalendar,
|
||||
aListener))
|
||||
{
|
||||
throw new Error ("processItipItem: " +
|
||||
"_processCalendarAction failed!");
|
||||
}
|
||||
break;
|
||||
case "REPLY":
|
||||
case "REFRESH":
|
||||
case "ADD":
|
||||
case "CANCEL":
|
||||
case "COUNTER":
|
||||
case "DECLINECOUNTER":
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
default:
|
||||
throw new Error("processItipItem: " +
|
||||
"Received unknown method: " +
|
||||
recvMethod);
|
||||
}
|
||||
this._isExistingItem(calItem, recvMethod, respMethod, targetCalendar,
|
||||
aListener);
|
||||
++i;
|
||||
calItem = itemList[i];
|
||||
}
|
||||
|
||||
// Send the appropriate response
|
||||
// figure out a good way to determine when a response is needed!
|
||||
if (recvMethod != respMethod) {
|
||||
this._getTransport().simpleSendResponse(respItipItem);
|
||||
}
|
||||
},
|
||||
|
||||
/* Continue processing the iTip Item now that we have determined whether
|
||||
* there is an existing item or not.
|
||||
*/
|
||||
_continueProcessingItem: function cipCPI(newItem, existingItem, recvMethod, respMethod,
|
||||
calAction, targetCalendar, aListener) {
|
||||
var transport = this._getTransport();
|
||||
switch (recvMethod) {
|
||||
case "REQUEST":
|
||||
// Only add to calendar if we accepted invite
|
||||
var replyStat = this._getReplyStatus(newItem,
|
||||
transport.defaultIdentity);
|
||||
if (replyStat == "DECLINED") {
|
||||
break;
|
||||
}
|
||||
// else fall through
|
||||
case "PUBLISH":
|
||||
if (!this._processCalendarAction(newItem,
|
||||
existingItem,
|
||||
calAction,
|
||||
targetCalendar,
|
||||
aListener))
|
||||
{
|
||||
throw new Error ("processItipItem: " +
|
||||
"_processCalendarAction failed!");
|
||||
}
|
||||
break;
|
||||
case "REPLY":
|
||||
case "REFRESH":
|
||||
case "ADD":
|
||||
case "CANCEL":
|
||||
case "COUNTER":
|
||||
case "DECLINECOUNTER":
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
default:
|
||||
throw new Error("processItipItem: " +
|
||||
"Received unknown method: " +
|
||||
recvMethod);
|
||||
}
|
||||
|
||||
// When replying, the reply must only contain the ORGANIZER and the
|
||||
// status of the ATTENDEE that represents ourselves. Therefore we must
|
||||
// remove all other ATTENDEEs from the itipItem we send back.
|
||||
@@ -195,32 +210,19 @@ calItipProcessor.prototype = {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
for (var j=0; j < itemList.length; ++j) {
|
||||
var respCalItem = itemList[j];
|
||||
var attendees = respCalItem.getAttendees({});
|
||||
var attendees = newItem.getAttendees({});
|
||||
|
||||
for each (var attendee in attendees) {
|
||||
// Leave the ORGANIZER alone.
|
||||
if (!attendee.isOrganizer) {
|
||||
// example: mailto:joe@domain.com
|
||||
var meString = idPrefix + me;
|
||||
if (attendee.id.toLowerCase() != meString.toLowerCase()) {
|
||||
respCalItem.removeAttendee(attendee);
|
||||
}
|
||||
for each (var attendee in attendees) {
|
||||
// Leave the ORGANIZER alone.
|
||||
if (!attendee.isOrganizer) {
|
||||
// example: mailto:joe@domain.com
|
||||
var meString = idPrefix + me;
|
||||
if (attendee.id.toLowerCase() != meString.toLowerCase()) {
|
||||
newItem.removeAttendee(attendee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send the appropriate response
|
||||
// figure out a good way to determine when a response is needed!
|
||||
if (recvMethod != respMethod) {
|
||||
transport.simpleSendResponse(respItipItem);
|
||||
}
|
||||
|
||||
// Yay it worked!
|
||||
// XXX TODO: Actually tie this to success/failure of the transport
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
@@ -344,6 +346,7 @@ calItipProcessor.prototype = {
|
||||
* calendar.
|
||||
*/
|
||||
_processCalendarAction: function cipPCA(aCalItem,
|
||||
aExistingItem,
|
||||
aOperation,
|
||||
aTargetCalendar,
|
||||
aListener) {
|
||||
@@ -356,6 +359,15 @@ calItipProcessor.prototype = {
|
||||
return true;
|
||||
|
||||
case CAL_ITIP_PROC_UPDATE_OP:
|
||||
// To udpate, we must require the existing item to be set
|
||||
if (!aExistingItem)
|
||||
throw new Error("_processCalendarAction: Item to update not found");
|
||||
|
||||
// TODO: Handle generation properly - Bug 418345
|
||||
aCalItem.generation = aExistingItem.generation;
|
||||
aTargetCalendar.modifyItem(aCalItem, aExistingItem, aListener);
|
||||
return true;
|
||||
|
||||
case CAL_ITIP_PROC_DELETE_OP:
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
@@ -383,5 +395,68 @@ calItipProcessor.prototype = {
|
||||
replyStatus = attendee.participationStatus;
|
||||
}
|
||||
return replyStatus;
|
||||
},
|
||||
|
||||
// A placeholder to make sure we don't try to add multiple items from the
|
||||
// onOperationComplete function.
|
||||
_handledID:null,
|
||||
|
||||
/**
|
||||
* Helper function to determine if this item already exists on this calendar
|
||||
* or not. It then calls _continueProcessingItem setting calAction and
|
||||
* existingItem appropirately
|
||||
*/
|
||||
_isExistingItem: function cipIEI(aCalItem, aRecvMethod, aRespMethod,
|
||||
aTargetCal, aListener) {
|
||||
|
||||
var foundItemListener = {
|
||||
itipProcessor: this,
|
||||
onOperationComplete:
|
||||
function (aCalendar, aStatus, aOperationType, aId, aDetail) {
|
||||
// If there is no item, we get this call with a failure error
|
||||
if (aStatus == Components.results.NS_ERROR_FAILURE &&
|
||||
!this.itipProcessor._handledID) {
|
||||
// Cache the id so that we know we've handled this one.
|
||||
this.itipProcessor._handledID = aCalItem.id;
|
||||
this.itipProcessor._continueProcessingItem(aCalItem,
|
||||
null,
|
||||
aRecvMethod,
|
||||
aRespMethod,
|
||||
CAL_ITIP_PROC_ADD_OP,
|
||||
aTargetCal,
|
||||
aListener);
|
||||
}
|
||||
},
|
||||
onGetResult:
|
||||
function onget(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
|
||||
// Now, if the old item exists, cache it and return true
|
||||
if (aCount && aItems[0]) {
|
||||
this.itipProcessor._continueProcessingItem(aCalItem,
|
||||
aItems[0],
|
||||
aRecvMethod,
|
||||
aRespMethod,
|
||||
CAL_ITIP_PROC_UPDATE_OP,
|
||||
aTargetCal,
|
||||
aListener);
|
||||
}
|
||||
}
|
||||
};
|
||||
aTargetCal.getItem(aCalItem.id, foundItemListener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Centralized location for obtaining the proper transport. This way it
|
||||
* will be easy to support multiple transports in the future
|
||||
* without changing the existing code too much.
|
||||
*/
|
||||
_getTransport: function cipGT() {
|
||||
// XXX Support for transports other than email go here.
|
||||
// For now we just assume it's email.
|
||||
var transport = Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"].
|
||||
createInstance(Components.interfaces.calIItipTransport);
|
||||
if (!transport) {
|
||||
throw new Error("iTipProcessor cannot instantiate transport");
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,29 +162,9 @@ function setupBar(imipMethod)
|
||||
imipBar.setAttribute("collapsed", "false");
|
||||
var description = document.getElementById("imip-description");
|
||||
|
||||
// Bug 348666: here is where we would check if this event was already
|
||||
// added to calendar or not and display correct information
|
||||
|
||||
if (imipMethod.toUpperCase() == "REQUEST") {
|
||||
if (description.firstChild.data) {
|
||||
description.firstChild.data = ltnGetString("lightning",
|
||||
"imipBarRequestText");
|
||||
}
|
||||
|
||||
var button = document.getElementById("imip-button1");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipAcceptInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
|
||||
|
||||
// Create a DECLINE button
|
||||
button = document.getElementById("imip-button2");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipDeclineInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('DECLINED', 'CONFIRMED');");
|
||||
// Check if this is an update and display things accordingly
|
||||
isUpdateMsg();
|
||||
} else if (imipMethod.toUpperCase() == "REPLY") {
|
||||
// Bug xxxx we currently cannot process REPLY messages so just let
|
||||
// the user know what this is, and don't give them any options.
|
||||
@@ -404,3 +384,140 @@ function finishItipAction(aOperationType, aStatus, aDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the list of events in the iTipItem and discovers whether or not
|
||||
* these events already exist on a calendar. Calls determineUpdateType.
|
||||
*/
|
||||
function isUpdateMsg()
|
||||
{
|
||||
// According to the specification, we have to determine if the event ID
|
||||
// already exists on the calendar of the user - that means we have to search
|
||||
// them all. :-(
|
||||
var isUpdate = 0;
|
||||
var existingItemSequence = -1;
|
||||
calendarList = getCalendarManager().getCalendars({});
|
||||
|
||||
// Create a composite
|
||||
compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
|
||||
.createInstance(Components.interfaces.calICompositeCalendar);
|
||||
|
||||
for(var i=0; i < calendarList.length; ++i) {
|
||||
compCal.addCalendar(calendarList[i]);
|
||||
}
|
||||
|
||||
// Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
|
||||
// same ID, this simplifies our searching, we can just look for Item[0].id
|
||||
var itemList = gItipItem.getItemList({ });
|
||||
var newSequence = itemList[0].getProperty("SEQUENCE");
|
||||
|
||||
var onFindItemListener = {
|
||||
processedId: null,
|
||||
onOperationComplete:
|
||||
function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
|
||||
if (!this.processedId){
|
||||
// Then the ID doesn't exist, don't call us twice
|
||||
this.processedId = true;
|
||||
determineUpdateType(newSequence, -1);
|
||||
}
|
||||
},
|
||||
|
||||
onGetResult:
|
||||
function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
|
||||
if (aCount && aItems[0] && !this.processedId) {
|
||||
this.processedId = true;
|
||||
determineUpdateType(newSequence, aItems[0].getProperty("SEQUENCE"));
|
||||
}
|
||||
}
|
||||
};
|
||||
// Search
|
||||
compCal.getItem(itemList[0].id, onFindItemListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what our update status is. It can return three things:
|
||||
* 0 = the new event does not exist on the calendar (therefore, this is an add)
|
||||
* 1 = the event does exist and contains a proper update (this is an update)
|
||||
* 2 = the event clicked on is an old update and should NOT be applied
|
||||
*/
|
||||
function determineUpdateType(newItemSequence, existingItemSequence) {
|
||||
// Three states here:
|
||||
// Item does not exist yet: existingItemSequence == -1
|
||||
// Item has been updated: newSequence > existingSequence
|
||||
// Item is an old message that has already been added/updated: new <= existing
|
||||
var isUpdate = 0;
|
||||
|
||||
if (existingItemSequence == -1)
|
||||
isUpdate = 0;
|
||||
else if (newItemSequence > existingItemSequence)
|
||||
isUpdate = 1;
|
||||
else
|
||||
isUpdate = 2;
|
||||
|
||||
// We now call our display code to display the proper message for this
|
||||
// update type
|
||||
displayRequestMethod(isUpdate);
|
||||
}
|
||||
|
||||
function displayRequestMethod(updateValue) {
|
||||
|
||||
var description = document.getElementById("imip-description");
|
||||
if (updateValue) {
|
||||
// This is a message updating existing event(s). But updateValue could
|
||||
// indicate that this update has already been applied, check that first.
|
||||
if (updateValue == 2) {
|
||||
// This case, they clicked on an old message that has already been
|
||||
// added/updated, we want to tell them that.
|
||||
if (description.firstChild.data) {
|
||||
description.firstChild.data = ltnGetString("lightning",
|
||||
"imipBarAlreadyAddedText");
|
||||
}
|
||||
|
||||
var button = document.getElementById("imip-button1");
|
||||
button.setAttribute("hidden", "true");
|
||||
button = document.getElementById("imip-button2");
|
||||
button.setAttribute("hidden", "true");
|
||||
} else {
|
||||
// Legitimate update, let's offer the update path
|
||||
if (description.firstChild.data) {
|
||||
description.firstChild.data = ltnGetString("lightning",
|
||||
"imipBarUpdateText");
|
||||
}
|
||||
|
||||
var button = document.getElementById("imip-button1");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipUpdateInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
|
||||
|
||||
// Create a DECLINE button (user chooses not to attend the updated event)
|
||||
button = document.getElementById("imip-button2");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipDeclineInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('DECLINED', 'CONFIRMED');");
|
||||
}
|
||||
} else {
|
||||
if (description.firstChild.data) {
|
||||
description.firstChild.data = ltnGetString("lightning",
|
||||
"imipBarRequestText");
|
||||
}
|
||||
|
||||
var button = document.getElementById("imip-button1");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipAcceptInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
|
||||
|
||||
// Create a DECLINE button
|
||||
button = document.getElementById("imip-button2");
|
||||
button.removeAttribute("hidden");
|
||||
button.setAttribute("label", ltnGetString("lightning",
|
||||
"imipDeclineInvitation.label"));
|
||||
button.setAttribute("oncommand",
|
||||
"setAttendeeResponse('DECLINED', 'CONFIRMED');");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user