Fix bug 734375 - Support Attachments and Attendees on Alarms. r=mmecca
authorPhilipp Kewisch <mozilla@kewis.ch>
Mon, 12 Mar 2012 09:51:42 +0100
changeset 11062 eb1745fc8a71656250c074a9ba88067482b18534
parent 11061 e74ad835b0f4d8333de373f65bff16907bfe4aec
child 11063 d5c44c6980e4f211f6c465a929d2e93e892a50af
push id463
push userbugzilla@standard8.plus.com
push dateTue, 24 Apr 2012 17:34:51 +0000
treeherdercomm-beta@e53588e8f7b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmmecca
bugs734375
Fix bug 734375 - Support Attachments and Attendees on Alarms. r=mmecca
calendar/base/public/calIAlarm.idl
calendar/base/src/calAlarm.js
calendar/test/unit/test_alarm.js
--- 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();