Fix
bug 734375 - Support Attachments and Attendees on Alarms. r=mmecca
--- a/calendar/base/public/calIAlarm.idl
+++ b/calendar/base/public/calIAlarm.idl
@@ -34,16 +34,18 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIVariant;
interface nsISimpleEnumerator;
+interface calIAttachment;
+interface calIAttendee;
interface calIDateTime;
interface calIDuration;
interface calIItemBase;
interface calIIcalComponent;
[scriptable, uuid(b8db7c7f-c168-4e11-becb-f26c1c4f5f8f)]
interface calIAlarm : nsISupports
{
@@ -139,27 +141,33 @@ interface calIAlarm : nsISupports
/**
* The summary of the alarm. Not valid for AUDIO and DISPLAY alarms.
*/
attribute AUTF8String summary;
/**
* Manage Attendee for this alarm. Not valid for AUDIO and DISPLAY alarms.
*/
- // TODO void addAttendee(in AUTF8String aAttendee);
- // TODO void deleteAttendee(in AUTF8String aAttendee);
+ void addAttendee(in calIAttendee aAttendee);
+ void deleteAttendee(in calIAttendee aAttendee);
+ void clearAttendees();
+ void getAttendees(out PRUint32 count,
+ [array,size_is(count),retval] out calIAttendee attendees);
/**
* Manage Attachments for this alarm.
* For EMAIL alarms, more than one attachment can be specified.
* For AUDIO alarms, one Attachment can be specified.
* For DISPLAY alarms, attachments are invalid.
*/
- // TODO void addAttachment(in AUTF8String aAttachment);
- // TODO void deleteAttachment(in AUTF8String aAttachment);
+ void addAttachment(in calIAttachment aAttachment);
+ void deleteAttachment(in calIAttachment aAttachment);
+ void clearAttachments();
+ void getAttachments(out PRUint32 count,
+ [array,size_is(count),retval] out calIAttachment attachments);
/**
* The human readable representation of this alarm. Uses locale strings.
*
* @param aItem The item to base the string on. Defaults to an event.
*/
AUTF8String toString([optional] in calIItemBase aItem);
--- a/calendar/base/src/calAlarm.js
+++ b/calendar/base/src/calAlarm.js
@@ -153,17 +153,21 @@ calAlarm.prototype = {
"mDuration",
"mLastAck"];
for each (let member in simpleMembers) {
m[member] = this[member];
}
for each (let member in arrayMembers) {
- m[member] = this[member].slice(0);
+ let newArray = [];
+ for each (let oldElem in this[member]) {
+ newArray.push(oldElem.clone());
+ }
+ m[member] = newArray;
}
for each (let member in objectMembers) {
if (this[member] && this[member].clone) {
m[member] = this[member].clone();
} else {
m[member] = this[member];
}
@@ -212,17 +216,16 @@ calAlarm.prototype = {
get action() {
return this.mAction || "DISPLAY";
},
set action(aValue) {
this.ensureMutable();
return (this.mAction = aValue);
},
- // TODO Do we really need to expose this?
get description() {
if (this.action == "AUDIO") {
return null;
}
return this.mDescription;
},
set description(aValue) {
this.ensureMutable();
@@ -314,37 +317,94 @@ calAlarm.prototype = {
let alarmDate = this.mAbsoluteDate.clone();
// All Day events are handled as 00:00:00
alarmDate.isDate = false;
return alarmDate.addDuration(this.mDuration);
},
- get attendees() {
- return this.mAttendees;
+ getAttendees: function getAttendees(aCount) {
+ let attendees;
+ if (this.action == "AUDIO" || this.action == "DISPLAY") {
+ attendees = [];
+ } else {
+ attendees = this.mAttendees.concat([]);
+ }
+ aCount.value = attendees.length;
+ return attendees;
},
- set attendees(aValue) {
- this.ensureMutable();
- // TODO Make add/update/deleteAttendee
- return (this.mAttendees = aValue);
+
+ addAttendee: function addAttendee(aAttendee) {
+ // Make sure its not duplicate
+ this.deleteAttendee(aAttendee);
+
+ // Now check if its valid
+ if (this.action == "AUDIO" || this.action == "DISPLAY") {
+ throw new Error("Alarm type AUDIO/DISPLAY may not have attendees");
+ }
+
+ // And add it (again)
+ this.mAttendees.push(aAttendee);
+ },
+
+ deleteAttendee: function deleteAttendee(aAttendee) {
+ let deleteId = aAttendee.id;
+ for (let i = 0; i < this.mAttendees.length; i++) {
+ if (this.mAttendees[i].id == deleteId) {
+ this.mAttendees.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ clearAttendees: function clearAttendees() {
+ this.mAttendees = [];
},
- get attachments() {
+ getAttachments: function getAttachments(aCount) {
+ let attachments;
if (this.action == "AUDIO") {
- return this.mAttachments.splice(1);
+ attachments = (this.mAttachments.length ? [this.mAttachments[0]] : []);
} else if (this.action == "DISPLAY") {
- return [];
+ attachments = [];
+ } else {
+ attachments = this.mAttachments.concat([]);
}
- return this.mAttachments;
+ aCount.value = attachments.length;
+ return attachments;
},
- set attachments(aValue) {
- this.ensureMutable();
- // TODO Make add/update/deleteAttendee
- return (this.mAttachments = aValue);
+
+ addAttachment: function addAttachment(aAttachment) {
+ // Make sure its not duplicate
+ this.deleteAttachment(aAttachment);
+
+ // Now check if its valid
+ if (this.action == "AUDIO" && this.mAttachments.length) {
+ throw new Error("Alarm type AUDIO may only have one attachment");
+ } else if (this.action == "DISPLAY") {
+ throw new Error("Alarm type DISPLAY may not have attachments");
+ }
+
+ // And add it (again)
+ this.mAttachments.push(aAttachment);
+ },
+
+ deleteAttachment: function deleteAttachment(aAttachment) {
+ let deleteHash = aAttachment.hashId;
+ for (let i = 0; i < this.mAttachments.length; i++) {
+ if (this.mAttachments[i].hashId == deleteHash) {
+ this.mAttachments.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ clearAttachments: function clearAttachments() {
+ this.mAttachments = [];
},
get icalString() {
let comp = this.icalComponent;
return (comp ? comp.serializeToICS() : "");
},
set icalString(val) {
this.ensureMutable();
@@ -397,38 +457,37 @@ calAlarm.prototype = {
repeatProp.value = this.repeat;
durationProp.valueAsIcalString = this.duration.icalString;
comp.addProperty(repeatProp);
comp.addProperty(durationProp);
}
// Set up attendees (REQUIRED for EMAIL action)
- /* TODO add support for attendees
- if (this.action == "EMAIL" && !this.attendees.length) {
+ /* TODO should we be strict here?
+ if (this.action == "EMAIL" && !this.getAttendees({}).length) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
} */
- for each (let attendee in this.attendees) {
- let attendeeProp = icssvc.createIcalProperty("ATTENDEE");
- attendeeProp.value = attendee;
- comp.addProperty(attendeeProp);
+ for each (let attendee in this.getAttendees({})) {
+ comp.addProperty(attendee.icalProperty);
}
// Set up attachments (REQUIRED for AUDIO and EMAIL types, there MUST
// NOT be more than one for AUDIO.
- /* TODO add support for attachments
- if ((this.action == "EMAIL" || this.action == "AUDIO") &&
- !this.attachments.length) {
+ if (this.action == "AUDIO" && this.getAttachments({}).length != 1) {
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+ }
+
+ /* TODO should we be strict here?
+ if (this.action == "EMAIL" && !this.attachments.length) {
throw Components.results.NS_ERROR_NOT_INITIALIZED;
} */
- for each (let attachment in this.attachments) {
- let attachmentProp = icssvc.createIcalProperty("ATTACH");
- attachmentProp.value = attachment;
- comp.addProperty(attachmentProp);
+ for each (let attachment in this.getAttachments({})) {
+ comp.addProperty(attachment.icalProperty);
}
// Set up summary (REQUIRED for EMAIL)
if (this.summary || this.action == "EMAIL") {
let summaryProp = icssvc.createIcalProperty("SUMMARY");
// Summary needs to have a non-empty value
summaryProp.value = this.summary ||
calGetString("calendar", "alarmDefaultSummary");
@@ -517,25 +576,29 @@ calAlarm.prototype = {
} else if (durationProp || repeatProp) {
throw Components.results.NS_ERROR_INVALID_ARG;
} else {
this.duration = null;
this.repeat = 0;
}
// Set up attendees
- this.attendees = [];
- for (let attendee in cal.ical.propertyIterator(aComp, "ATTENDEE")) {
- // XXX this.addAttendee(attendee);
+ this.clearAttendees();
+ for (let attendeeProp in cal.ical.propertyIterator(aComp, "ATTENDEE")) {
+ let attendee = cal.createAttendee();
+ attendee.icalProperty = attendeeProp;
+ this.addAttendee(attendee);
}
// Set up attachments
- this.attachments = [];
- for (let attach in cal.ical.propertyIterator(aComp, "ATTACH")) {
- // XXX this.addAttachment(attach);
+ this.clearAttachments();
+ for (let attachProp in cal.ical.propertyIterator(aComp, "ATTACH")) {
+ let attach = cal.createAttachment();
+ attach.icalProperty = attachProp;
+ this.addAttachment(attach);
}
// Set up summary
this.summary = (summaryProp ? summaryProp.value : null);
// Set up description
this.description = (descriptionProp ? descriptionProp.value : null);
--- a/calendar/test/unit/test_alarm.js
+++ b/calendar/test/unit/test_alarm.js
@@ -77,17 +77,25 @@ function test_display_alarm() {
// Set a Description, REQUIRED for ACTION:DISPLAY
alarm.description = "test";
do_check_eq(alarm.description, "test");
// SUMMARY is not valid for ACTION:DISPLAY
alarm.summary = "test";
do_check_eq(alarm.summary, null);
- // TODO No attendees
+ // No attendees allowed
+ let attendee = cal.createAttendee();
+ attendee.id = "mailto:horst";
+
+ try {
+ alarm.addAttendee(attendee);
+ do_throw("DISPLAY alarm should not be able to save attendees");
+ } catch (e) {}
+
dump("Done\n");
}
function test_email_alarm() {
dump("Testing EMAIL alarms...");
let alarm = cal.createAlarm();
// Set ACTION to DISPLAY, make sure this was not rejected
alarm.action = "EMAIL";
@@ -96,39 +104,104 @@ function test_email_alarm() {
// Set a Description, REQUIRED for ACTION:EMAIL
alarm.description = "description";
do_check_eq(alarm.description, "description");
// Set a Summary, REQUIRED for ACTION:EMAIL
alarm.summary = "summary";
do_check_eq(alarm.summary, "summary");
- // TODO check for at least one attendee
+ // Check for at least one attendee
+ let attendee1 = cal.createAttendee();
+ attendee1.id = "mailto:horst";
+ let attendee2 = cal.createAttendee();
+ attendee2.id = "mailto:gustav";
+
+ do_check_eq(alarm.getAttendees({}).length, 0);
+ alarm.addAttendee(attendee1);
+ do_check_eq(alarm.getAttendees({}).length, 1);
+ alarm.addAttendee(attendee2);
+ do_check_eq(alarm.getAttendees({}).length, 2);
+ alarm.addAttendee(attendee1);
+ let addedAttendees = alarm.getAttendees({});
+ do_check_eq(addedAttendees.length, 2);
+ do_check_eq(addedAttendees[0], attendee2);
+ do_check_eq(addedAttendees[1], attendee1);
+
+ alarm.deleteAttendee(attendee1);
+ do_check_eq(alarm.getAttendees({}).length, 1);
+
+ alarm.clearAttendees();
+ do_check_eq(alarm.getAttendees({}).length, 0);
// TODO test attachments
dump("Done\n");
}
function test_audio_alarm() {
dump("Testing AUDIO alarms...");
let alarm = cal.createAlarm();
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE;
+ alarm.alarmDate = cal.createDateTime();
// Set ACTION to AUDIO, make sure this was not rejected
alarm.action = "AUDIO";
do_check_eq(alarm.action, "AUDIO");
// No Description for ACTION:AUDIO
alarm.description = "description";
do_check_eq(alarm.description, null);
// No Summary, for ACTION:AUDIO
alarm.summary = "summary";
- do_check_eq(alarm.description, null);
+ do_check_eq(alarm.summary, null);
+
+ // No attendees allowed
+ let attendee = cal.createAttendee();
+ attendee.id = "mailto:horst";
+
+ try {
+ alarm.addAttendee(attendee);
+ do_throw("AUDIO alarm should not be able to save attendees");
+ } catch (e) {}
+
+ // Test attachments
+ let sound = cal.createAttachment();
+ sound.uri = makeURL("file:///sound.wav");
+ let sound2 = cal.createAttachment();
+ sound2.uri = makeURL("file:///sound2.wav");
- // TODO No attendees
- // TODO test for one attachment
+ // Adding an attachment should work
+ alarm.addAttachment(sound);
+ let addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 1);
+ do_check_eq(addedAttachments[0], sound);
+ do_check_true(alarm.icalString.indexOf("ATTACH:file:///sound.wav") > -1);
+
+ // Adding twice shouldn't change anything
+ alarm.addAttachment(sound);
+ addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 1);
+ do_check_eq(addedAttachments[0], sound);
+
+ try {
+ alarm.addAttachment(sound2);
+ do_throw("Adding a second alarm should fail for type AUDIO");
+ } catch (e) {}
+
+ // Deleting should work
+ alarm.deleteAttachment(sound);
+ addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 0);
+
+ // As well as clearing
+ alarm.addAttachment(sound);
+ alarm.clearAttachments();
+ addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 0);
+
dump("Done\n");
}
function test_custom_alarm() {
dump("Testing X-SMS (custom) alarms...");
let alarm = cal.createAlarm();
// Set ACTION to a custom value, make sure this was not rejected
alarm.action = "X-SMS"
@@ -137,19 +210,57 @@ function test_custom_alarm() {
// There is no restriction on DESCRIPTION for custom alarms
alarm.description = "description";
do_check_eq(alarm.description, "description");
// There is no restriction on SUMMARY for custom alarms
alarm.summary = "summary";
do_check_eq(alarm.summary, "summary");
- // TODO test for attendees
- // TODO test for attachments
- dump("Done\n");
+ // Test for attendees
+ let attendee1 = cal.createAttendee();
+ attendee1.id = "mailto:horst";
+ let attendee2 = cal.createAttendee();
+ attendee2.id = "mailto:gustav";
+
+ do_check_eq(alarm.getAttendees({}).length, 0);
+ alarm.addAttendee(attendee1);
+ do_check_eq(alarm.getAttendees({}).length, 1);
+ alarm.addAttendee(attendee2);
+ do_check_eq(alarm.getAttendees({}).length, 2);
+ alarm.addAttendee(attendee1);
+ do_check_eq(alarm.getAttendees({}).length, 2);
+
+ alarm.deleteAttendee(attendee1);
+ do_check_eq(alarm.getAttendees({}).length, 1);
+
+ alarm.clearAttendees();
+ do_check_eq(alarm.getAttendees({}).length, 0);
+
+ // Test for attachments
+ let attach1 = cal.createAttachment();
+ attach1.uri = makeURL("file:///example.txt");
+ let attach2 = cal.createAttachment();
+ attach2.uri = makeURL("file:///example2.txt");
+
+ alarm.addAttachment(attach1);
+ alarm.addAttachment(attach2);
+
+ let addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 2);
+ do_check_eq(addedAttachments[0], attach1);
+ do_check_eq(addedAttachments[1], attach2);
+
+ alarm.deleteAttachment(attach1);
+ addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 1);
+
+ alarm.clearAttachments();
+ addedAttachments = alarm.getAttachments({});
+ do_check_eq(addedAttachments.length, 0);
}
// Check if any combination of REPEAT and DURATION work as expected.
function test_repeat() {
dump("Testing REPEAT and DURATION properties...");
let message;
let alarm = cal.createAlarm();