From fafabf48c5a3958b82c4034ba35b3ad8a6b916bb Mon Sep 17 00:00:00 2001 From: "ctalbert%mozilla.com" Date: Wed, 20 Feb 2008 18:16:59 +0000 Subject: [PATCH] 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 --- mozilla/calendar/base/src/calItipProcessor.js | 193 ++++++++++++------ .../calendar/lightning/content/imip-bar.js | 161 +++++++++++++-- 2 files changed, 273 insertions(+), 81 deletions(-) diff --git a/mozilla/calendar/base/src/calItipProcessor.js b/mozilla/calendar/base/src/calItipProcessor.js index c5d2f54fae4..d5be4f77fae 100644 --- a/mozilla/calendar/base/src/calItipProcessor.js +++ b/mozilla/calendar/base/src/calItipProcessor.js @@ -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; } } diff --git a/mozilla/calendar/lightning/content/imip-bar.js b/mozilla/calendar/lightning/content/imip-bar.js index 2ee8686e713..bd956ae1150 100644 --- a/mozilla/calendar/lightning/content/imip-bar.js +++ b/mozilla/calendar/lightning/content/imip-bar.js @@ -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');"); + } +}