bug 334685 - Add iTIP/iMIP support. r=dbo

git-svn-id: svn://10.0.0.236/trunk@221456 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
ctalbert.moz%gmail.com 2007-03-07 19:39:11 +00:00
parent ceb2af1a6f
commit c09b85cd46
15 changed files with 478 additions and 263 deletions

View File

@ -1,4 +1,3 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
@ -21,6 +20,7 @@
# Contributor(s):
# Dan Mosedale <dan.mosedale@oracle.com>
# Mike Shaver <shaver@off.net>
# Matthew Willis <lilmatt@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -65,6 +65,8 @@ XPIDLSRCS = calIAlarmService.idl \
calIICSService.idl \
calIItemBase.idl \
calIItipItem.idl \
calIItipProcessor.idl \
calIItipTransport.idl \
calIRecurrenceInfo.idl \
calIRecurrenceDate.idl \
calIRecurrenceDateSet.idl \

View File

@ -97,11 +97,6 @@
#define CAL_RECURRENCEINFO_CONTRACTID \
"@mozilla.org/calendar/recurrence-info;1"
#define CAL_ITIPITEM_CID \
{ 0xb84de879, 0x4b85, 0x4d68, { 0x85, 0x50, 0xe0, 0xc5, 0x27, 0xe4, 0x6f, 0x98 } }
#define CAL_ITIPITEM_CONTRACTID \
"@mozilla.org/calendar/itip-item;1"
#define NS_ERROR_CALENDAR_WRONG_COMPONENT_TYPE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_CALENDAR, 1)
// Until extensible xpconnect error mapping works
// #define NS_ERROR_CALENDAR_IMMUTABLE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_CALENDAR, 2)

View File

@ -241,6 +241,12 @@ interface calIItemBase : nsISupports
// removeAttendee/addAttendee that follow the call to getAttendees.
void getAttendees(out PRUint32 count,
[array,size_is(count),retval] out calIAttendee attendees);
/**
* getAttendeeById's matching is done in a case-insensitive manner to handle
* places where "MAILTO:" or similar properties are capitalized arbitrarily
* by different calendar clients.
*/
calIAttendee getAttendeeById(in AUTF8String id);
void removeAttendee(in calIAttendee attendee);
void addAttendee(in calIAttendee attendee);

View File

@ -19,7 +19,8 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <cmtalbert@myfastmail.com>
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -46,14 +47,20 @@ interface nsISimpleEnumerator;
* parser, the imip-bar UI, and the iTIP processor. It encapsulates a list of
* calIItemBase objects and provides specialized iTIP methods for those items.
*/
[scriptable, uuid(B84DE879-4B85-4d68-8550-E0C527E46F98)]
[scriptable, uuid(f41392ab-dcad-4bad-818f-b3d1631c4d93)]
interface calIItipItem : nsISupports
{
/**
/**
* Initializes the item with an ics string
* @arg - in parameter - AString of ical Data
* @param - in parameter - AString of ical Data
*/
void init(in AString icalData);
void init(in AUTF8String icalData);
/**
* Creates a new calItipItem with the same attributes as the one that
* clone() is called upon.
*/
calIItipItem clone();
/**
* Attribute: isSend - set to TRUE when sending this item to initiate an
@ -68,14 +75,14 @@ interface calIItipItem : nsISupports
/**
* Attribute: receivedMethod - method the iTIP item had upon reciept
*/
attribute AString receivedMethod;
attribute AUTF8String receivedMethod;
/**
* Attribute: responseMethod - method that the protocol handler (or the
* user) decides to use to respond to the iTIP item (could be COUNTER,
* REPLY, DECLINECOUNTER, etc)
*/
attribute AString responseMethod;
attribute AUTF8String responseMethod;
/**
* Attribute: autoResponse Set to one of the three constants below
@ -104,33 +111,47 @@ interface calIItipItem : nsISupports
*/
attribute calICalendar targetCalendar;
/**
* localStatus: The response that the user has made to the invitation in
* this ItipItem.
*/
attribute AUTF8String localStatus;
/**
* Get the list of items that are encapsulated in this calIItipItem
* @returns An array of calIItemBase items that are inside this
* calIItipItem
*/
void getItemList(out unsigned long itemCount,
[retval, array, size_is(itemCount)] out calIItemBase items);
/**
* Get the first item from the iTIP message
* Bug XXX 351761: Need to find a way to make this use an nsISimpleEnumerator
* @return calIItemBase
*/
calIItemBase getFirstItem();
*/
/**
* Get next item from the iTIP message. If there is no next item then it
* returns NULL
* @return calIItemBase
*/
calIItemBase getNextItem();
*/
/**
* Modifies a calIItemBase that is in the component list. Internally, the
* interface will update the proper component. It does this via the
* UID of the component by calling hasSameIds().
* @arg in parameter - item to modify
* @param in parameter - item to modify
* @return returns the new calIItemBase object for convienence
*/
calIItemBase modifyItem(in calIItemBase item);
/**
* Modifies the state of the given attendee in the item's ics
* @arg in parameter - AString containing attendee address
* @arg in parameter - AString contianing the new attendee status
* @param in parameter - AString containing attendee address
* @param in parameter - AString contianing the new attendee status
*/
void setAttendeeStatus(in AString attendeeId, in AString status);
};

View File

@ -83,10 +83,11 @@ EXTRA_SCRIPTS = \
calCalendarManager.js \
calDateTimeFormatter.js \
calEvent.js \
calItemBase.js \
calItipItem.js \
calIcsParser.js \
calIcsSerializer.js \
calItemBase.js \
calItipItem.js \
calItipProcessor.js \
calProtocolHandler.js \
calRecurrenceInfo.js \
calTodo.js \

View File

@ -162,6 +162,9 @@ calEvent.prototype = {
var calcomp = icssvc.createIcalComponent("VCALENDAR");
calcomp.prodid = "-//Mozilla Calendar//NONSGML Sunbird//EN";
calcomp.version = "2.0";
if (this.hasProperty("METHOD")) {
calcomp.method = this.getProperty("METHOD");
}
calcomp.addSubcomponent(this.icalComponent);
return calcomp.serializeToICS();
},

View File

@ -23,6 +23,7 @@
* Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
* Mike Shaver <shaver@off.net>
* Joey Minta <jminta@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -408,9 +409,14 @@ calItemBase.prototype = {
getAttendeeById: function (id) {
var attendees = this.getAttendees({});
for (var i = 0; i < attendees.length; i++)
if (attendees[i].id == id)
return attendees[i];
var lowerCaseId = id.toLowerCase();
for each (var attendee in attendees) {
// This match must be case insensitive to deal with differing
// cases of things like MAILTO:
if (attendee.id.toLowerCase() == lowerCaseId) {
return attendee;
}
}
return null;
},
@ -418,8 +424,10 @@ calItemBase.prototype = {
this.modify();
var found = false, newAttendees = [];
var attendees = this.getAttendees({});
var attIdLowerCase =attendee.id.toLowerCase();
for (var i = 0; i < attendees.length; i++) {
if (attendees[i].id != attendee.id)
if (attendees[i].id.toLowerCase() != attIdLowerCase)
newAttendees.push(attendees[i]);
else
found = true;

View File

@ -22,6 +22,7 @@
* Contributor(s):
* Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
* Mike Shaver <shaver@off.net>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -139,11 +140,16 @@ const componentData =
script: "calWeekTitleService.js",
constructor: "calWeekTitleService"},
{cid: Components.ID("{b84de879-4b85-4d68-8550-e0C527e46f98}"),
{cid: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"),
contractid: "@mozilla.org/calendar/itip-item;1",
script: "calItipItem.js",
constructor: "calItipItem"},
{cid: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"),
contractid: "@mozilla.org/calendar/itip-processor;1",
script: "calItipProcessor.js",
constructor: "calItipProcessor"},
{cid: Components.ID("{1e2fc0e2-bf5f-4d60-9f1e-5e92cf517c0b}"),
contractid: "@mozilla.org/network/protocol;1?name=webcal",
script: "calProtocolHandler.js",

View File

@ -19,7 +19,8 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <cmtalbert@myfastmail.com>
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -44,37 +45,29 @@ function calItipItem() {
this.mCurrentItemIndex = 0;
}
var calItipItemClassInfo = {
getInterfaces: function (count) {
calItipItem.prototype = {
getInterfaces: function ciiGI(count) {
var ifaces = [
Components.interfaces.nsISupports,
Components.interfaces.calIItipItem,
Components.interfaces.nsIClassInfo
Ci.nsIClassInfo,
Ci.nsISupports,
Ci.calIItipItem
];
count.value = ifaces.length;
return ifaces;
},
getHelperForLanguage: function (language) {
getHelperForLanguage: function ciiGHFL(aLanguage) {
return null;
},
contractID: "@mozilla.org/calendar/itip-item;1",
classDescription: "Calendar iTIP item",
classID: Components.ID("{b84de879-4b85-4d68-8550-e0C527e46f98}"),
classID: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"),
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
flags: 0
};
flags: 0,
calItipItem.prototype = {
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIClassInfo)) {
return calItipItemClassInfo;
}
if (aIID.equals(Ci.nsISupports) || aIID.equals(Ci.calIItipItem)) {
return this;
} else {
QueryInterface: function ciiQI(aIid) {
if (!aIid.equals(Ci.nsISupports) && !aIid.equals(Ci.calIItipItem)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
@ -82,108 +75,156 @@ calItipItem.prototype = {
},
mIsSend: false,
get isSend() {
return this.mIsSend;
},
set isSend(value) {
this.mIsSend = value;
set isSend(aValue) {
return (this.mIsSend = aValue);
},
mReceivedMethod: null,
get receivedMethod() {
return this.mReceivedMethod;
},
set receivedMethod(value) {
this.mReceivedMethod = value;
set receivedMethod(aMethod) {
return (this.mReceivedMethod = aMethod.toUpperCase());
},
mResponseMethod: null,
get responseMethod() {
return this.mResponseMethod;
if (this.mIsInitialized) {
var method = null;
for each (var prop in this.mPropertiesList) {
if (prop.propertyName == "METHOD") {
method = prop.value;
break;
}
}
return method;
} else {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
},
set responseMethod(value) {
this.mResponseMethod = value;
set responseMethod(aMethod) {
this.mResponseMethod = aMethod.toUpperCase();
// Setting this also sets the global method attribute inside the
// encapsulated VCALENDAR.
if (this.mIsInitialized) {
var methodExists = false;
for each (var prop in this.mPropertiesList) {
if (prop.propertyName == "METHOD") {
methodExists = true;
prop.value = this.mResponseMethod;
}
}
if (!methodExists) {
var newProp = { propertyName: "METHOD",
value: this.mResponseMethod };
this.mPropertiesList.push(newProp);
}
} else {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
return this.mResponseMethod;
},
mAutoResponse: null,
get autoResponse() {
return this.mAutoResponse;
},
set autoResponse(value) {
this.mAutoResponse = value;
set autoResponse(aValue) {
return (this.mAutoResponse = aValue);
},
mTargetCalendar: null,
get targetCalendar() {
return this.mTargetCalendar;
},
set targetCalendar(value) {
this.mTargetCalendar = value;
set targetCalendar(aValue) {
return (this.mTargetCalendar = aValue);
},
modifyItem: function(item) {
// Bug 348666: This will be used when we support delegation and COUNTER
mLocalStatus: null,
get localStatus() {
return this.mLocalStatus;
},
set localStatus(aValue) {
return (this.mLocalStatus = aValue);
},
modifyItem: function ciiMI(item) {
// Bug 348666: This will be used when we support delegation and COUNTER.
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
mItemList: null,
mPropertiesList: null,
init: function(icalData) {
this.mItemList = new Array();
var icsService = Cc["@mozilla.org/calendar/ics-service;1"]
.getService(Components.interfaces.calIICSService);
var rootComp = icsService.parseICS(icalData);
var calComp;
// libical returns the vcalendar component if there is just
// one vcalendar. If there are multiple vcalendars, it returns
// an xroot component, with those vcalendar children. We need to
// handle both cases.
if (rootComp.componentType == 'VCALENDAR') {
calComp = rootComp;
} else {
calComp = rootComp.getFirstSubcomponent('VCALENDAR');
init: function ciiI(aIcalString) {
var parser = Cc["@mozilla.org/calendar/ics-parser;1"].
createInstance(Ci.calIIcsParser);
parser.parseString(aIcalString);
this.mItemList = parser.getItems({});
this.mPropertiesList = parser.getProperties({});
// We set both methods now for safety's sake. It's the ItipProcessor's
// responsibility to properly ascertain what the correct response
// method is (using user feedback, prefs, etc.) for the given
// receivedMethod. The RFC tells us to treat items without a METHOD
// as if they were METHOD:REQUEST.
var method;
for each (var prop in this.mPropertiesList) {
if (prop.propertyName == "METHOD") {
method = prop.value;
}
}
// Get the method property out of the calendar to set our methods
var method = calComp.method;
// We set both methods now for safety's sake. It is the iTIP
// processor's responsibility to properly ascertain what the correct
// response method is (using user feedback, pref's etc) for this given
// ReceivedMethod
this.mReceivedMethod = method;
this.mResponseMethod = method;
while (calComp) {
var subComp = calComp.getFirstSubcomponent("ANY");
while (subComp) {
switch (subComp.componentType) {
case "VEVENT":
var event = Cc["@mozilla.org/calendar/event;1"]
.createInstance(Ci.calIEvent);
event.icalComponent = subComp;
this.mItemList.push(event);
break;
case "VTODO":
var todo = Cc["@mozilla.org/calendar/todo;1"]
.createInstance(Ci.calITodo);
todo.icalComponent = subComp;
this.mItemList.push(todo);
break;
default:
// Nothing -- Bug 185537: Implement VFREEBUSY, VJOURNAL
}
subComp = calComp.getNextSubcomponent("ANY");
}
calComp = rootComp.getNextSubcomponent('VCALENDAR');
}
this.mIsInitialized = true;
},
getFirstItem: function() {
clone: function ciiC() {
// Iterate through all the calItems in the original calItipItem to
// concatenate all the calItems' icalStrings.
var icalString = "";
var itemList = this.getItemList({ });
for (var i = 0; i < itemList.length; i++) {
icalString += itemList[i].icalString;
}
// Create a new calItipItem and initialize it using the icalString
// from above.
var newItem = Cc["@mozilla.org/calendar/itip-item;1"].
createInstance(Ci.calIItipItem);
newItem.init(icalString);
// Copy over the exposed attributes.
newItem.receivedMethod = this.receivedMethod;
newItem.responseMethod = this.responseMethod;
newItem.autoResponse = this.autoResponse;
newItem.targetCalendar = this.targetCalendar;
newItem.localStatus = this.localStatus;
newItem.isSend = this.isSend;
return newItem;
},
/**
* This returns both the array and the number of items. An easy way to
* call it is: var itemArray = itipItem.getItemList({ });
*/
getItemList: function ciiGIL(itemCountRef) {
if (!this.mIsInitialized || (this.mItemList.length == 0)) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
itemCountRef.value = this.mItemList.length;
return this.mItemList;
},
/*getFirstItem: function ciiGFI() {
if (!this.mIsInitialized || (this.mItemList.length == 0)) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
@ -191,7 +232,7 @@ calItipItem.prototype = {
return this.mItemList[0];
},
getNextItem: function() {
getNextItem: function ciiGNI() {
if (!this.mIsInitialized || (this.mItemList.length == 0)) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
}
@ -201,22 +242,44 @@ calItipItem.prototype = {
} else {
return null;
}
},
},*/
setAttendeeStatus: function (attendeeID, status) {
// Note that this code forces the user to respond to all items
// in the same way, which is a current limitation of the spec
if (attendeeID.match(/mailto:/i)) {
attendeeID = "mailto:" + attendeeID; // prepend mailto
/**
* Note that this code forces the user to respond to all items in the same
* way, which is a current limitation of the spec.
*/
setAttendeeStatus: function ciiSAS(aAttendeeId, aStatus) {
// Append "mailto:" to the attendee if it is missing it.
var attId = aAttendeeId.toLowerCase();
if (!attId.match(/mailto:/i)) {
attId = "mailto:" + attId;
}
for (var i=0; i < this.mItemList.length; ++i) {
var item = this.mItemList[i];
var attendee = item.getAttendeeById(attendeeID);
for each (var item in this.mItemList) {
var attendee = item.getAttendeeById(attId);
if (attendee) {
// XXX BUG 351589: workaround for updating an attendee
item.removeAttendee(attendee);
attendee.participationStatus = status;
item.addAttendee(attendee);
// Replies should not have the RSVP property.
// Since attendee.deleteProperty("RSVP") doesn't work, we must
// create a new attendee from scratch WITHOUT the RSVP
// property and copy in the other relevant data.
// XXX use deleteProperty after bug 358498 is fixed.
newAttendee = Cc["@mozilla.org/calendar/attendee;1"].
createInstance(Ci.calIAttendee);
if (attendee.commonName) {
newAttendee.commonName = attendee.commonName;
}
if (attendee.role) {
newAttendee.role = attendee.role;
}
if (attendee.userType) {
newAttendee.userType = attendee.userType;
}
newAttendee.id = attendee.id;
newAttendee.participationStatus = aStatus;
item.addAttendee(newAttendee);
}
}
}

View File

@ -222,7 +222,9 @@ bin\js\calIcsImportExport.js
bin\js\calIcsParser.js
bin\js\calIcsSerializer.js
bin\js\calItemBase.js
bin\js\calItipEmailTransport.js
bin\js\calItipItem.js
bin\js\calItipProcessor.js
bin\js\calListFormatter.js
bin\js\calMonthGridPrinter.js
bin\js\calOutlookCSVImportExport.js

View File

@ -54,6 +54,7 @@ DIRS = ../../db/sqlite3/src ../../storage \
../libical ../base ../providers \
../import-export \
components \
../itip \
$(NULL)
# Select a theme from which to pull our skin goodness

View File

@ -20,7 +20,8 @@
*
* Contributor(s):
* Mike Shaver <shaver@mozilla.org>
* Clint Talbert <cmtalbert@myfastmail.com>
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -45,8 +46,8 @@ function makeTableRow(val) {
function getLightningStringBundle()
{
var svc = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService);
var svc = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
return svc.createBundle("chrome://lightning/locale/lightning.properties");
}
@ -107,18 +108,23 @@ function createHtml(event)
html.body.table.appendChild(createHtmlTableSection(labelText,
event.startDate.jsDate.toLocaleString()));
if (event.organizer &&
(event.organizer.commonName || event.organizer.id)) {
if (event.organizer &&
(event.organizer.commonName || event.organizer.id))
{
labelText = stringBundle.GetStringFromName("imipHtml.organizer");
var orgname = event.organizer.commonName || event.organizer.id;
// Trim any instances of "mailto:" for better readibility.
var orgname = event.organizer.commonName ||
event.organizer.id.replace(/mailto:/ig, "");
html.body.table.appendChild(createHtmlTableSection(labelText, orgname));
}
var eventDescription = event.getProperty("DESCRIPTION");
if (eventDescription) {
// Remove the useless "Outlookism" squiggle.
var desc = eventDescription.replace("*~*~*~*~*~*~*~*~*~*", "");
labelText = stringBundle.GetStringFromName("imipHtml.description");
html.body.table.appendChild(createHtmlTableSection(labelText,
eventDescription));
html.body.table.appendChild(createHtmlTableSection(labelText,desc));
}
}
@ -128,32 +134,48 @@ function createHtml(event)
function ltnMimeConverter() { }
ltnMimeConverter.prototype = {
QueryInterface: function (aIID) {
QueryInterface: function QI(aIID) {
if (!aIID.equals(Ci.nsISupports) &&
!aIID.equals(Ci.nsISimpleMimeConverter))
throw Ci.NS_ERROR_NO_INTERFACE;
{
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
convertToHTML: function(contentType, data) {
var event = Cc["@mozilla.org/calendar/event;1"]
.createInstance(Ci.calIEvent);
mUri: null,
get uri() {
return this.mUri;
},
set uri(aUri) {
return (this.mUri = aUri);
},
convertToHTML: function lmcCTH(contentType, data) {
var event = Cc["@mozilla.org/calendar/event;1"].
createInstance(Ci.calIEvent);
event.icalString = data;
var html = createHtml(event);
try {
// Bug 351610: This mechanism is a little flawed
var itipItem = Cc["@mozilla.org/calendar/itip-item;1"]
.createInstance(Ci.calIItipItem);
var itipItem = Cc["@mozilla.org/calendar/itip-item;1"].
createInstance(Ci.calIItipItem);
itipItem.init(data);
var observer = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
if (observer) {
observer.notifyObservers(itipItem, "onItipItemCreation", 0);
}
// this.mUri is the message URL that we are processing.
// We use it to get the nsMsgHeaderSink to store the calItipItem.
var msgUrl = this.mUri.QueryInterface(Ci.nsIMsgMailNewsUrl);
var sinkProps = msgUrl.msgWindow.msgHeaderSink.properties;
sinkProps.setPropertyAsInterface("itipItem", itipItem);
// Notify the observer that the itipItem is available
var observer = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
observer.notifyObservers(null, "onItipItemCreation", 0);
} catch (e) {
Components.utils.reportError("Cannot Create iTIP Item: " + e);
Components.utils.reportError("convertToHTML: " +
"Cannot create itipItem: " + e);
}
return html;
@ -161,30 +183,30 @@ ltnMimeConverter.prototype = {
};
var myModule = {
registerSelf: function (compMgr, fileSpec, location, type) {
registerSelf: function RS(aCompMgr, aFileSpec, aLocation, aType) {
debug("*** Registering Lightning text/calendar handler\n");
compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
var compMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.myCID,
"Lightning text/calendar handler",
this.myContractID,
fileSpec,
location,
type);
aFileSpec,
aLocation,
aType);
var catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
.getService(Ci.nsICategoryManager);
catman.addCategoryEntry("simple-mime-converters", "text/calendar",
this.myContractID, true, true);
},
getClassObject: function (compMgr, cid, iid) {
if (!cid.equals(this.myCID))
getClassObject: function GCO(aCompMgr, aCid, aIid) {
if (!aCid.equals(this.myCID)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Components.interfaces.nsIFactory))
}
if (!aIid.equals(Components.interfaces.nsIFactory)) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
return this.myFactory;
},
@ -193,15 +215,15 @@ var myModule = {
myContractID: "@mozilla.org/lightning/mime-converter;1",
myFactory: {
createInstance: function (outer, iid) {
if (outer != null)
createInstance: function mfCI(aOuter, aIid) {
if (aOuter != null) {
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new ltnMimeConverter()).QueryInterface(iid);
}
return (new ltnMimeConverter()).QueryInterface(aIid);
}
},
canUnload: function(compMgr) {
canUnload: function CU(aCompMgr) {
return true;
}
};
@ -209,4 +231,3 @@ var myModule = {
function NSGetModule(compMgr, fileSpec) {
return myModule;
}

View File

@ -19,7 +19,8 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert <cmtalbert@myfastmail.com>
* Clint Talbert <ctalbert.moz@gmail.com>
* Matthew Willis <lilmatt@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -34,62 +35,68 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* This bar lives inside the message window, its lifetime is the lifetime of
* the main thunderbird message window.
* This bar lives inside the message window.
* Its lifetime is the lifetime of the main thunderbird message window.
*/
var gItipItem;
/**
* Sets up iTIP creation event listener
* When the mime parser discovers a text/calendar attachment to a message,
* it creates a calIItipItem and calls an observer. We watch for that
* observer. Bug 351610 is open so that we can find a way to make this
* communication mechanism more robust.
*/
const onItipItem = {
observe: function observe(subject, topic, state) {
if (topic == "onItipItemCreation") {
try {
var itipItem =
subject.QueryInterface(Components.interfaces.calIItipItem);
// We are only called upon receipt of an invite, so
// ensure that isSend is false.
itipItem.isSend = false;
// Until the iTIPResponder code lands, we do no response
// at all.
itipItem.autoResponse = Components.interfaces.calIItipItem.NONE;
itipItem.targetCalendar = getTargetCalendar();
var imipMethod = getMsgImipMethod();
if (imipMethod.length) {
itipItem.receivedMethod = imipMethod;
} else {
// Thunderbird 1.5 case, we cannot get the iMIPMethod
imipMethod = itipItem.receivedMethod;
}
gItipItem = itipItem;
// XXX Bug 351742: no security yet
// handleImipSecurity(imipMethod);
setupBar(imipMethod);
} catch (e) {
Components.utils.reportError(e);
}
checkForItipItem();
}
}
};
addEventListener('messagepane-loaded', imipOnLoad, true);
addEventListener('messagepane-unloaded', imipOnUnload, true);
function checkForItipItem()
{
var itipItem;
try {
var msgUri = GetLoadedMessage();
var sinkProps = msgWindow.msgHeaderSink.properties;
// This property was set by LightningTextCalendarConverter.js
itipItem = sinkProps.getPropertyAsInterface("itipItem",
Components.interfaces.calIItipItem)
} catch (e) {
// This will throw on every message viewed that doesn't have the
// itipItem property set on it. So we eat the errors and move on.
// XXX TODO: Only swallow the errors we need to. Throw all others.
return;
}
// We are only called upon receipt of an invite, so ensure that isSend
// is false.
itipItem.isSend = false;
// XXX Get these from preferences
itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
itipItem.targetCalendar = getTargetCalendar();
var imipMethod = getMsgImipMethod();
if (imipMethod.length) {
itipItem.receivedMethod = imipMethod;
} else {
// Thunderbird 1.5 case, we cannot get the imipMethod
imipMethod = itipItem.receivedMethod;
}
gItipItem = itipItem;
// XXX Bug 351742: no S/MIME or spoofing protection yet
// handleImipSecurity(imipMethod);
setupBar(imipMethod);
}
addEventListener("messagepane-loaded", imipOnLoad, true);
addEventListener("messagepane-unloaded", imipOnUnload, true);
/**
* Attempt to add self to gMessageListeners defined in msgHdrViewOverlay.js
* Add self to gMessageListeners defined in msgHdrViewOverlay.js
*/
function imipOnLoad()
{
@ -99,18 +106,20 @@ function imipOnLoad()
gMessageListeners.push(listener);
// Set up our observers
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(onItipItem, "onItipItemCreation", false);
var observerSvc = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
observerSvc.addObserver(onItipItem, "onItipItemCreation", false);
}
function imipOnUnload()
{
removeEventListener('messagepane-loaded', imipOnLoad, true);
removeEventListener('messagepane-unloaded', imipOnUnload, true);
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(onItipItem, "onItipItemCreation");
removeEventListener("messagepane-loaded", imipOnLoad, true);
removeEventListener("messagepane-unloaded", imipOnUnload, true);
var observerSvc = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
observerSvc.removeObserver(onItipItem, "onItipItemCreation");
gItipItem = null;
}
@ -119,25 +128,27 @@ function onImipStartHeaders()
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("collapsed", "true");
// New Message is starting, clear our iMIP/iTIP stuff so that we don't set
// it by accident
// A new message is starting.
// Clear our iMIP/iTIP stuff so it doesn't contain stale information.
imipMethod = "";
gItipItem = null;
}
// We need an onEndHeader or else MessageListner will throw. However,
// we do not need to actually do anything in this function.
/**
* Required by MessageListener. no-op
*/
function onImipEndHeaders()
{
// no-op
}
function setupBar(imipMethod)
{
// XXX - Bug 348666 - Currently we only do PUBLISH requests
// XXX - Bug 348666 - Currently we only do REQUEST requests
// In the future this function will set up the proper actions
// and attributes for the buttons as based on the iMIP Method
var imipBar = document.getElementById("imip-bar");
imipBar.setAttribute("collapsed","false");
imipBar.setAttribute("collapsed", "false");
var description = document.getElementById("imip-description");
// Bug 348666: here is where we would check if this event was already
@ -146,13 +157,22 @@ function setupBar(imipMethod)
description.firstChild.data = ltnGetString("lightning","imipBarText");
}
// Since there is only a PUBLISH, this is easy
var button1 = document.getElementById("imip-btn1");
button1.removeAttribute("hidden");
button1.setAttribute("label", ltnGetString("lightning",
"imipAddToCalendar.label"));
button1.setAttribute("oncommand",
"setAttendeeResponse('PUBLISH', 'CONFIRMED');");
var button = document.getElementById("imip-button1");
button.removeAttribute("hidden");
button.setAttribute("label", ltnGetString("lightning",
"imipAcceptInvitation.label"));
button.setAttribute("oncommand",
"setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
if (imipMethod == "REQUEST") {
// Then 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');");
}
}
function getMsgImipMethod()
@ -171,10 +191,40 @@ function getMsgRecipient()
var msgURI = GetLoadedMessage();
var msgHdr = messenger.messageServiceFromURI(msgURI)
.messageURIToMsgHdr(msgURI);
// msgHdr.recipients is always the one recipient - the one looking at
// the email, which is who we are interested in.
// msgHdr recipients can be a comma separated list of recipients.
// We then compare against the defaultIdentity to find ourselves.
// XXX This won't always work:
// Users with multiple accounts and invites going to the non-default
// account, Users with email aliases where the defaultIdentity.email
// doesn't match the recipient, etc.
if (msgHdr) {
imipRecipient = msgHdr.recipients;
var recipientList = msgHdr.recipients;
// Remove any spaces
recipientList = recipientList.split(" ").join("");
recipientList = recipientList.split(",");
var emailSvc = Cc["@mozilla.org/calendar/itip-transport;1?type=email"].
getService(Ci.calIItipTransport);
var me = emailSvc.defaultIdentity;
var lt;
var gt;
for each (var recipient in recipientList) {
// Deal with <foo@bar.com> style addresses
lt = recipient.indexOf("<");
gt = recipient.indexOf(">");
// I chose 6 since <a@b.c> is the shortest technically valid email
// address I could come up with.
if ((lt >= 0) && (gt >= 6)) {
recipient = recipient.substring(lt+1, gt);
}
if (recipient.toLowerCase() == me.toLowerCase()) {
imipRecipient = recipient;
}
}
}
return imipRecipient;
}
@ -184,8 +234,8 @@ function getMsgRecipient()
*/
function getTargetCalendar()
{
var calMgr = Components.classes["@mozilla.org/calendar/manager;1"]
.getService(Components.interfaces.calICalendarManager);
var calMgr = Cc["@mozilla.org/calendar/manager;1"].
getService(Ci.calICalendarManager);
var cals = calMgr.getCalendars({});
return cals[0];
}
@ -198,69 +248,97 @@ function setAttendeeResponse(type, eventStatus)
{
var myAddress = getMsgRecipient();
if (type && gItipItem) {
switch (type) { // We set the attendee status appropriately
// We set the attendee status appropriately
switch (type) {
case "ACCEPTED":
case "TENTATIVE":
case "DECLINED":
gItipItem.setAttendeeStatus(myAddress, type);
doResponse();
break;
// fall through
case "REPLY":
case "PUBLISH":
doResponse(eventStatus);
break;
default:
// Nothing -- if given nothing then the attendee wishes to
// disregard the mail, so no further action required
// no-op. The attendee wishes to disregard the mail, so no
// further action is required.
break;
}
}
finishItipAction(type, eventStatus);
}
/**
* doResponse performs the iTIP action for the current iTIPItem that we
* doResponse performs the iTIP action for the current ItipItem that we
* parsed from the email.
* Takes an optional parameter to set the event STATUS property
* @param aLocalStatus optional parameter to set the event STATUS property.
* aLocalStatus can be empty, "TENTATIVE", "CONFIRMED", or "CANCELLED"
*/
function doResponse(eventStatus)
function doResponse(aLocalStatus)
{
// XXX For now, just add the item to the calendar
// calIOperationListener so that we can properly return status to the
// imip-bar
var operationListener = {
onOperationComplete:
function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
// Call finishItipAction to set the status of the operation
finishItipAction(aOperationType, aStatus, aDetail);
},
onGetResult:
function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
// no-op
}
};
// The spec is unclear if we must add all the items or if the
// user should get to pick which item gets added.
var cal = getTargetCalendar();
var item = gItipItem.getFirstItem();
while (item != null) {
if (eventStatus.length)
item.status = eventStatus;
cal.addItem(item, null);
item = gItipItem.getNextItem();
if (aLocalStatus != null) {
gItipItem.localStatus = aLocalStatus;
}
var itipProc = Cc["@mozilla.org/calendar/itip-processor;1"].
createInstance(Ci.calIItipProcessor);
itipProc.processItipItem(gItipItem, operationListener);
}
/**
* Bug 348666 (complete iTIP support) - This gives the user an indication
* that the Action occurred.
* In the future we want to store the status of invites that you have added
* to your calendar and provide the ability to request updates from the
* organizer. This function will be responsible for setting up and
* maintaining the proper lists of events and informing the user as to the
* state of the iTIP action
* Additionally, this function should ONLY be called once we KNOW what the
* status of the event addition was. It must wait for a clear signal from the
* iTIP Processor as to whether or not the event added/failed to add or
* what have you. It should alert the user to the true status of the operation
*
* In the future, this will store the status of the invitation in the
* invitation manager. This will enable us to provide the ability to request
* updates from the organizer and to suggest changes to invitations.
*
* Currently, this is called from our calIOperationListener that is sent to
* the ItipProcessor. This conveys the status of the local iTIP processing
* on your calendar. It does not convey the success or failure of sending a
* response to the ItipItem.
*/
function finishItipAction(type, eventStatus)
function finishItipAction(aOperationType, aStatus, aDetail)
{
// For now, we just do something very simple
var description = document.getElementById("imip-description");
if (description.firstChild.data) {
description.firstChild.data = ltnGetString("lightning",
"imipAddedItemToCal");
// For now, we just state the status for the user something very simple
var desc = document.getElementById("imip-description");
if (desc.firstChild != null) {
if (Components.isSuccessCode(aStatus)) {
desc.firstChild.data = ltnGetString("lightning",
"imipAddedItemToCal");
document.getElementById("imip-button1").setAttribute("hidden",
true);
document.getElementById("imip-button2").setAttribute("hidden",
true);
} else {
// Bug 348666: When we handle more iTIP methods, we need to create
// more sophisticated error handling.
document.getElementById("imip-bar").setAttribute("collapsed", true);
var msg = "Invitation could not be processed. Status: " + aStatus;
if (aDetail) {
msg += "\nDetails: " + aDetail;
}
// Defined in import-export
showError(msg);
}
}
document.getElementById("imip-btn1").setAttribute("hidden", true);
}

View File

@ -226,7 +226,8 @@
&lightning.imipbar.description;
</description>
<spacer flex="1"/>
<button id="imip-btn1" align="right"/>
<button id="imip-button1" align="center" pack="end"/>
<button id="imip-button2" align="center" pack="end" hidden="true"/>
</hbox>
</hbox>
</vbox>

View File

@ -20,6 +20,7 @@
#
# Contributor(s):
# Matthew Willis <lilmatt@mozilla.com>
# Clint Talbert <ctalbert.moz@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
@ -55,3 +56,9 @@ imipHtml.description=Description:
imipAddToCalendar.label=Add To Calendar
imipAddedItemToCal=Event Added to Calendar
imipBarText=This message contains an invitation to an event.
imipAcceptInvitation.label=Accept
imipDeclineInvitation.label=Decline
itipReplySubject=Event Invitation Reply: %1$S
itipReplyBodyAccept=%1$S has accepted your event invitation.
itipReplyBodyDecline=%1$S has declined your event invitation.