[california/wip/731543-attendees: 5/5] Using Attendee class rather than strings for storage



commit 9c3a9ede326821d9a8b179062042e48442d53b14
Author: Jim Nelson <jim yorba org>
Date:   Tue Nov 4 19:20:26 2014 -0800

    Using Attendee class rather than strings for storage

 po/POTFILES.in                        |    1 +
 po/POTFILES.skip                      |    1 +
 src/Makefile.am                       |    1 +
 src/component/component-attendee.vala |  108 +++++++++++++++++++++++++++++++++
 src/component/component-instance.vala |   64 ++++++++------------
 5 files changed, 136 insertions(+), 39 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b458ec9..f8d396f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -14,6 +14,7 @@ src/calendar/calendar-day-of-week.vala
 src/calendar/calendar-exact-time-span.vala
 src/calendar/calendar.vala
 src/component/component.vala
+src/component/component-instance.vala
 src/component/component-recurrence-rule.vala
 src/host/host-create-update-event.vala
 src/host/host-create-update-recurring.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index ac5b36b..09d8111 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -12,6 +12,7 @@ src/calendar/calendar-day-of-week.c
 src/calendar/calendar-exact-time-span.c
 src/component/component.c
 src/component/component-event.c
+src/component/component-instance.c
 src/component/component-recurrence-rule.c
 src/host/host-create-update-event.c
 src/host/host-import-calendar.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 1bb8c3b..c4d6bdf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -85,6 +85,7 @@ california_VALASOURCES = \
        collection/collection-simple-iterable.vala \
        \
        component/component.vala \
+       component/component-attendee.vala \
        component/component-date-time.vala \
        component/component-details-parser.vala \
        component/component-error.vala \
diff --git a/src/component/component-attendee.vala b/src/component/component-attendee.vala
new file mode 100644
index 0000000..7690463
--- /dev/null
+++ b/src/component/component-attendee.vala
@@ -0,0 +1,108 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Component {
+
+/**
+ * An immutable representation of an iCalendar ATTENDEE.
+ *
+ * For equality purposes, only the { link mailto} is used.  All other parameters are ignored.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.1]]
+ */
+
+public class Attendee : BaseObject, Gee.Hashable<Attendee> {
+    /**
+     * The mailto: of the ATTENDEE, the only required value for the property.
+     */
+    public Soup.URI mailto { get; private set; }
+    
+    /**
+     * The CN (common name) for the ATTENDEE.
+     */
+    public string? common_name { get; private set; default = null; }
+    
+    /**
+     * The { link mailto} URI as a string.
+     */
+    public string mailto_string { owned get {
+        return mailto.to_string(false);
+    } }
+    
+    /**
+     * The { link mailto} URI as a simple (unadorned) RFC822 mailbox (i.e. email address).
+     */
+    public string mailbox { owned get {
+        return mailto.path;
+    } }
+    
+    private Gee.HashSet<string> parameters = new Gee.HashSet<string>(String.ci_hash, String.ci_equal);
+    
+    /**
+     * Create an Attendee with the required { link mailto} and optional { link common_name}.
+     */
+    public Attendee(Soup.URI mailto, string? common_name) throws ComponentError {
+        validate_mailto(mailto);
+        
+        this.mailto = mailto;
+        this.common_name = common_name;
+    }
+    
+    internal Attendee.from_property(iCal.icalproperty prop) throws Error {
+        string attendee = prop.get_attendee();
+        if (String.is_empty(attendee))
+            throw new ComponentError.INVALID("Invalid attendee property: no value");
+        
+        mailto = URI.parse(attendee);
+        validate_mailto(mailto);
+        
+        unowned iCal.icalparameter? param = prop.get_first_parameter(iCal.icalparameter_kind.ANY_PARAMETER);
+        while (param != null) {
+            parameters.add(param.as_ical_string());
+            
+            // parse parameter into well-known (common) property
+            switch (param.isa()) {
+                case iCal.icalparameter_kind.CN_PARAMETER:
+                    common_name = param.get_cn();
+                break;
+                
+                default:
+                    // fall-through
+                break;
+            }
+            
+            param = prop.get_next_parameter(iCal.icalparameter_kind.ANY_PARAMETER);
+        }
+    }
+    
+    private static void validate_mailto(Soup.URI uri) throws ComponentError {
+        if (uri.scheme != "mailto" || String.is_empty(uri.path))
+            throw new ComponentError.INVALID("Invalid attendee mailto: %s", uri.to_string(false));
+    }
+    
+    internal iCal.icalproperty as_ical_property() {
+        iCal.icalproperty prop = new iCal.icalproperty.attendee(mailto_string);
+        foreach (string parameter in parameters)
+            prop.add_parameter(new iCal.icalparameter.from_string(parameter));
+        
+        return prop;
+    }
+    
+    public uint hash() {
+        return String.ci_hash(mailto_string);
+    }
+    
+    public bool equal_to(Attendee other) {
+        return (this != other) ? mailto.equal(other.mailto) : true;
+    }
+    
+    public override string to_string() {
+        return mailto_string;
+    }
+}
+
+}
+
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index acdb8b3..1002fa6 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -174,16 +174,16 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     /**
      * ATTENDEEs for a VEVENT, VTODO, or VJOURNAL.
      *
-     * This property returns a read-only view of the list of attendees.  To add or remove attendees,
-     * use { link add_attendees}, { link remove_attendees}, and { link clear_attendees}.
-     * Because those methods always update the property itself and not merely modify the list,
-     * the property can be watched for changes with the "notify" and/or "altered" signals.
+     * To add or remove attendees, use { link add_attendees}, { link remove_attendees}, and
+     * { link clear_attendees}. Because those methods always update the property itself and not
+     * merely modify the list, the property can be watched for changes with the "notify" and/or
+     * "altered" signals.
      *
      * No validity checking is performed on attendee mailto's.
      *
      * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.1]]
      */
-    public Gee.List<string> attendees { get; private set; default = new Gee.ArrayList<string>(); }
+    public Gee.Set<Attendee> attendees { get; private set; default = new Gee.HashSet<Attendee>(); }
     
     /**
      * The iCal component being represented by this { link Instance}.
@@ -371,9 +371,11 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
         unowned iCal.icalproperty? attendee_prop = ical_component.get_first_property(
             iCal.icalproperty_kind.ATTENDEE_PROPERTY);
         while (attendee_prop != null) {
-            unowned string mailto = attendee_prop.get_attendee();
-            if (!String.is_empty(mailto))
-                attendees.add(mailto);
+            try {
+                attendees.add(new Attendee.from_property(attendee_prop));
+            } catch (ComponentError comperr) {
+                debug("Unable to parse ATTENDEE for %s: %s", to_string(), comperr.message);
+            }
             
             attendee_prop = ical_component.get_next_property(iCal.icalproperty_kind.ATTENDEE_PROPERTY);
         }
@@ -425,14 +427,9 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
             break;
             
             case PROP_ATTENDEES:
-                // TODO: Need to update iCal component with adjusted set of attendees, whether
-                // removed or appended
-                /*
-                unowned iCal.icalproperty? attendee_prop = ical_component.get_first_property(
-                    iCal.icalproperty_kind.ATTENDEE_PROPERTY);
-                while (attendee_prop != null) {
-                }
-                */
+                remove_all_properties(iCal.icalproperty_kind.ATTENDEE_PROPERTY);
+                foreach (Attendee attendee in attendees)
+                    ical_component.add_property(attendee.as_ical_property());
             break;
             
             default:
@@ -491,9 +488,9 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      * No URI-format checking is performed here; it is up to the caller to convert and deal with
      * formatting issues.  Also note that duplicates are allowed in the attendees list.
      */
-    public void add_attendees(Gee.Collection<string> mailtos) {
-        Gee.List<string> copy = traverse<string>(attendees).to_array_list();
-        copy.add_all(mailtos);
+    public void add_attendees(Gee.Collection<Attendee> to_add) {
+        Gee.Set<Attendee> copy = traverse<Attendee>(attendees).to_hash_set();
+        copy.add_all(to_add);
         
         attendees = copy;
     }
@@ -503,17 +500,17 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      *
      * See { link add_attendees} for notes about data validity and checking.
      */
-    public void remove_attendees(Gee.Collection<string> mailtos) {
-        attendees = traverse<string>(attendees)
-            .filter(attendee => mailtos.contains(attendee))
-            .to_array_list();
+    public void remove_attendees(Gee.Collection<Attendee> to_remove) {
+        attendees = traverse<Attendee>(attendees)
+            .filter(attendee => !to_remove.contains(attendee))
+            .to_hash_set();
     }
     
     /*
      * Removes all { link attendees}.
      */
     public void clear_attendees() {
-        attendees = new Gee.ArrayList<string>();
+        attendees = new Gee.HashSet<Attendee>();
     }
     
     /**
@@ -522,23 +519,12 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      * Returns null if no attendees are associated with this { link Instance}.
      */
     public string? attendees_to_string() {
-        return traverse<string>(attendees).to_string(stringify_attendee);
+        return traverse<Attendee>(attendees).to_string(stringify_attendee);
     }
     
-    private static string? stringify_attendee(string attendee, bool is_first, bool is_last) {
-        // Must parse correctly into URI
-        Soup.URI mailto;
-        try {
-            mailto = URI.parse(attendee);
-        } catch (Error err) {
-            return null;
-        }
-        
-        // Must be a non-empty mailto:
-        if (mailto.scheme != "mailto" || String.is_empty(mailto.path))
-            return null;
-        
-        return is_first ? mailto.path : ", %s".printf(mailto.path);
+    private static string? stringify_attendee(Attendee attendee, bool is_first, bool is_last) {
+        // A common separator for email addresses followed by an email address itself.
+        return is_first ? attendee.mailbox : _(", %s").printf(attendee.mailbox);
     }
     
     /**


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]