[california/wip/725788-recurring: 1/2] Display recurring events
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725788-recurring: 1/2] Display recurring events
- Date: Sat, 8 Mar 2014 03:16:05 +0000 (UTC)
commit b3011af8fbd1b7848c93479258be5c7c8e1a4c9a
Author: Jim Nelson <jim yorba org>
Date: Fri Mar 7 18:10:06 2014 -0800
Display recurring events
Component tracking now recognizes that Instances with the same UID
may exist, it's other properties which determine equality.
src/Makefile.am | 3 +
.../backing-calendar-source-subscription.vala | 152 +++++++++++---------
.../backing-eds-calendar-source-subscription.vala | 8 +-
src/backing/eds/backing-eds-calendar-source.vala | 2 +-
src/component/component-date-time.vala | 46 ++++++-
src/component/component-event.vala | 68 +++++++++-
src/component/component-instance.vala | 93 ++++++++++--
src/component/component-recurrable.vala | 67 +++++++++
src/component/component-sequenceable.vala | 48 ++++++
src/util/util-memory.vala | 27 ++++
src/view/month/month-controllable.vala | 20 ++-
11 files changed, 437 insertions(+), 97 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index e8942d2..fc98016 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -57,6 +57,8 @@ california_VALASOURCES = \
component/component-error.vala \
component/component-event.vala \
component/component-instance.vala \
+ component/component-recurrable.vala \
+ component/component-sequenceable.vala \
component/component-uid.vala \
component/component-vtype.vala \
\
@@ -67,6 +69,7 @@ california_VALASOURCES = \
host/host-popup.vala \
host/host-show-event.vala \
\
+ util/util-memory.vala \
util/util-string.vala \
\
view/view.vala \
diff --git a/src/backing/backing-calendar-source-subscription.vala
b/src/backing/backing-calendar-source-subscription.vala
index cdbac28..90245d5 100644
--- a/src/backing/backing-calendar-source-subscription.vala
+++ b/src/backing/backing-calendar-source-subscription.vala
@@ -41,35 +41,36 @@ public abstract class CalendarSourceSubscription : BaseObject {
public bool active { get; protected set; default = false; }
/**
- * Fired as existing { link Component.Event}s are discovered when starting a subscription.
+ * Fired as existing { link Component.Instance}s are discovered when starting a subscription.
*
* This is fired while { link start} is working, either in the foreground or in the background.
* It won't fire until start() is invoked.
*/
- public signal void event_discovered(Component.Event event);
+ public signal void instance_discovered(Component.Instance instance);
/**
- * Indicates that an event within the { link window} has been added to the calendar.
+ * Indicates that an { link Instance} within the { link window} has been added to the calendar.
*
* The signal is fired for both local additions (added through this interface) and remote
* additions.
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_added(Component.Event event);
+ public signal void instance_added(Component.Instance instance);
/**
- * Indicates that an event within the { link date_window} has been removed from the calendar.
+ * Indicates that an { link Instance} within the { link date_window} has been removed from the
+ * calendar.
*
* The signal is fired for both local removals (added through this interface) and remote
* removals.
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_removed(Component.Event event);
+ public signal void instance_removed(Component.Instance instance);
/**
- * Indicates that an event within the { link date_window} has been altered.
+ * Indicates that an { link Instance} within the { link date_window} has been altered.
*
* This is fired after the alterations have been made. Since the { link Component.Instance}s
* are mutable, it's possible to monitor their properties for changes and be notified that way.
@@ -79,13 +80,13 @@ public abstract class CalendarSourceSubscription : BaseObject {
*
* This signal won't fire until { link start} is called.
*/
- public signal void event_altered(Component.Event event);
+ public signal void instance_altered(Component.Instance instance);
/**
- * Indicates than the event within the { link date_window} has been dropped due to the
- * { link Source} going unavailable.
+ * Indicates than the { link Instance} within the { link date_window} has been dropped due to
+ * the { link Source} going unavailable.
*
- * Generally all the subscription's events will be reported one after another, but this
+ * Generally all the subscription's instances will be reported one after another, but this
* shouldn't be relied upon.
*
* Since the Source is now unavailable, this indicates that the Subscription will not be
@@ -96,7 +97,7 @@ public abstract class CalendarSourceSubscription : BaseObject {
* best course is to call { link Source.set_unavailable} and override
* { link notify_events_dropped} to perform internal bookkeeping.
*/
- public signal void event_dropped(Component.Event event);
+ public signal void instance_dropped(Component.Instance instance);
/**
* Fired if { link start} failed.
@@ -110,8 +111,10 @@ public abstract class CalendarSourceSubscription : BaseObject {
*/
public signal void start_failed(Error err);
- private Gee.HashMap<Component.UID, Component.Event> events = new Gee.HashMap<
- Component.UID, Component.Event>();
+ // Although Component.Instance has no simple notion of one UID for multiple instances, its
+ // subclasses (i.e. Event) do
+ private Gee.HashMultiMap<Component.UID, Component.Instance> instances = new Gee.HashMultiMap<
+ Component.UID, Component.Instance>();
protected CalendarSourceSubscription(CalendarSource calendar, Calendar.ExactTimeSpan window) {
this.calendar = calendar;
@@ -121,7 +124,8 @@ public abstract class CalendarSourceSubscription : BaseObject {
}
/**
- * Add a pre-existing { link Component.Event} to the subscription and notify subscribers.
+ * Add a @link Component.Instance} discovered while starting the subscription to the
+ * internal collection of instances and notify subscribers.
*
* As with the other notify_*() methods, subclasses should invoke this method to fire the
* signal rather than do it directly. This gives { link CalenderSourceSubscription} the
@@ -129,68 +133,89 @@ public abstract class CalendarSourceSubscription : BaseObject {
*
* It can also be overridden by a subclass to take action before or after the signal is fired.
*
- * @see event_discovered
+ * @see instance_discovered
*/
- protected virtual void notify_event_discovered(Component.Event event) {
- if (!events.has_key(event.uid)) {
- events.set(event.uid, event);
- event_discovered(event);
- } else {
- debug("Cannot add discovered event %s to %s: already known", event.to_string(), to_string());
- }
+ protected virtual void notify_instance_discovered(Component.Instance instance) {
+ if (add_instance(instance))
+ instance_discovered(instance);
+ else
+ debug("Cannot add discovered component %s to %s: already known", instance.to_string(),
to_string());
}
/**
- * Add a new { link Component.Event} to the subscription and notify subscribers.
+ * Add a new { link Component.Instance} to the subscription and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_added
+ * @see notify_instance_discovered
+ * @see instance_added
*/
- protected virtual void notify_event_added(Component.Event event) {
- if (!events.has_key(event.uid)) {
- events.set(event.uid, event);
- event_added(event);
- } else {
- debug("Cannot add event %s to %s: already known", event.to_string(), to_string());
- }
+ protected virtual void notify_instance_added(Component.Instance instance) {
+ if (add_instance(instance))
+ instance_added(instance);
+ else
+ debug("Cannot add component %s to %s: already known", instance.to_string(), to_string());
}
/**
- * Remove an { link Component.Event} from the subscription and notify subscribers.
+ * Remove an { link Component.Instance} from the subscription and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_removed
+ * @see notify_instance_discovered
+ * @see instance_removed
*/
- protected virtual void notify_event_removed(Component.UID uid) {
- Component.Event? event;
- if (events.unset(uid, out event))
- event_removed(event);
- else
+ protected virtual void notify_instance_removed(Component.UID uid) {
+ Gee.Collection<Component.Instance> removed_instances;
+ if (remove_instance(uid, out removed_instances)) {
+ foreach (Component.Instance instance in removed_instances)
+ instance_removed(instance);
+ } else {
debug("Cannot remove UID %s from %s: not known", uid.to_string(), to_string());
+ }
}
/**
- * Update an altered { link Component.Event} and notify subscribers.
+ * Update an altered { link Component.Instance} and notify subscribers.
*
- * @see notify_event_discovered
- * @see event_altered
+ * @see notify_instance_discovered
+ * @see instance_altered
*/
- protected virtual void notify_event_altered(Component.Event event) {
- if (events.has_key(event.uid))
- event_altered(event);
+ protected virtual void notify_instance_altered(Component.Instance instance) {
+ if (instances.contains(instance.uid))
+ instance_altered(instance);
else
- debug("Cannot notify altered event %s in %s: not known", event.to_string(), to_string());
+ debug("Cannot notify altered component %s in %s: not known", instance.to_string(), to_string());
}
/**
- * Notify that the { link Component.Event}s have been dropped due to the { link Source} going
+ * Notify that the { link Component.Instance}s have been dropped due to the { link Source} going
* unavailable.
*/
- protected virtual void notify_event_dropped(Component.Event event) {
- if (this.events.unset(event.uid))
- event_dropped(event);
- else
- debug("Cannot notify dropped event %s in %s: not known", event.to_string(), to_string());
+ protected virtual void notify_instance_dropped(Component.Instance instance) {
+ Gee.Collection<Component.Instance> removed_instances;
+ if (remove_instance(instance.uid, out removed_instances)) {
+ foreach (Component.Instance removed_instance in removed_instances)
+ instance_dropped(removed_instance);
+ } else {
+ debug("Cannot notify dropped component %s in %s: not known", instance.to_string(), to_string());
+ }
+ }
+
+ private bool add_instance(Component.Instance instance) {
+ bool already_exists = instances.get(instance.uid).contains(instance);
+ if (!already_exists)
+ instances.set(instance.uid, instance);
+
+ return !already_exists;
+ }
+
+ private bool remove_instance(Component.UID uid, out Gee.Collection<Component.Instance>
removed_instances) {
+ bool removed = instances.contains(uid);
+ if (removed) {
+ removed_instances = instances.get(uid);
+ instances.remove_all(uid);
+ } else {
+ removed_instances = new Gee.ArrayList<Component.Instance>();
+ }
+
+ return removed;
}
/**
@@ -228,24 +253,19 @@ public abstract class CalendarSourceSubscription : BaseObject {
if (calendar.is_available)
return;
- foreach (Component.Event event in events.values.to_array())
- notify_event_dropped(event);
+ // Use to_array() so no iteration troubles when notify_instance_dropped removes it from
+ // the multimap
+ foreach (Component.Instance instance in instances.get_values().to_array())
+ notify_instance_dropped(instance);
}
/**
- * Returns an { link Component.Instance} for the { link Component.UID}.
+ * Returns all { link Component.Instance}s for the { link Component.UID}.
*
* @returns null if the UID has not been seen.
*/
- public Component.Instance? for_uid(Component.UID uid) {
- return events.get(uid);
- }
-
- /**
- * Returns a read-only Map of all known { link Component.Event}s.
- */
- public Gee.Map<Component.UID, Component.Event> get_events() {
- return events.read_only_view;
+ public Gee.Collection<Component.Instance>? for_uid(Component.UID uid) {
+ return instances.contains(uid) ? instances.get(uid) : null;
}
public override string to_string() {
diff --git a/src/backing/eds/backing-eds-calendar-source-subscription.vala
b/src/backing/eds/backing-eds-calendar-source-subscription.vala
index 050b3e8..e4faf20 100644
--- a/src/backing/eds/backing-eds-calendar-source-subscription.vala
+++ b/src/backing/eds/backing-eds-calendar-source-subscription.vala
@@ -97,7 +97,7 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
Component.Event? event = Component.Instance.convert(calendar, eds_component.get_icalcomponent())
as Component.Event;
if (event != null)
- notify_event_discovered(event);
+ notify_instance_discovered(event);
} catch (Error err) {
debug("Unable to generate discovered event for %s: %s", to_string(), err.message);
}
@@ -115,7 +115,7 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
try {
Component.Event? event = Component.Instance.convert(calendar, ical_component) as
Component.Event;
if (event != null)
- notify_event_added(event);
+ notify_instance_added(event);
} catch (Error err) {
debug("Unable to generate added event for %s: %s", to_string(), err.message);
}
@@ -136,13 +136,13 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
debug("Unable to update event %s: %s", event.to_string(), err.message);
}
- notify_event_altered(event);
+ notify_instance_altered(event);
}
}
private void on_objects_removed(SList<weak E.CalComponentId?> ids) {
foreach (weak E.CalComponentId id in ids)
- notify_event_removed(new Component.UID(id.uid));
+ notify_instance_removed(new Component.UID(id.uid));
}
}
diff --git a/src/backing/eds/backing-eds-calendar-source.vala
b/src/backing/eds/backing-eds-calendar-source.vala
index fafb524..d4e6ecc 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -62,7 +62,7 @@ internal class EdsCalendarSource : CalendarSource {
string? uid;
client.create_object_sync(instance.ical_component, out uid, cancellable);
- return (uid != null && uid[0] != '\0') ? new Component.UID(uid) : null;
+ return !String.is_empty(uid) ? new Component.UID(uid) : null;
}
public override async void update_component_async(Component.Instance instance,
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index 4a23b96..56fcb63 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -6,7 +6,15 @@
namespace California.Component {
-public class DateTime : BaseObject {
+/**
+ * An immutable representation of iCal's DATE and DATE-TIME property, which are often used
+ * interchangeably.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.4]] and
+ * [[https://tools.ietf.org/html/rfc5545#section-3.3.5]]
+ */
+
+public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateTime> {
/**
* The TZID for the iCal component and property kind.
*
@@ -42,10 +50,21 @@ public class DateTime : BaseObject {
public iCal.icaltimetype dt;
/**
+ * The iCal property type ("kind") for { link dt}.
+ */
+ public iCal.icalproperty_kind kind;
+
+ /**
* Creates a new { link DateTime} for the iCal component of the property kind.
*
- * Note that DTSTART_PROPERTY, DTEND_PROPERTY, and DTSTAMP_PROPERTY are the only properties
- * currently supported.
+ * Note that the only properties currently supported are:
+ * * DTSTART_PROPERTY
+ * * DTEND_PROPERTY
+ * * DTSTAMP_PROPERTY
+ * * RECURRENCEID_PROPERTY
+ *
+ * @throws ComponentError.UNAVAILABLE if not found
+ * @throws ComponentError.INVALID if not a valid DATE or DATE-TIME
*/
public DateTime(iCal.icalcomponent ical_component, iCal.icalproperty_kind ical_prop_kind)
throws ComponentError {
@@ -66,6 +85,10 @@ public class DateTime : BaseObject {
dt = prop.get_dtend();
break;
+ case iCal.icalproperty_kind.RECURRENCEID_PROPERTY:
+ dt = prop.get_recurrenceid();
+ break;
+
default:
assert_not_reached();
}
@@ -79,6 +102,8 @@ public class DateTime : BaseObject {
unowned iCal.icalparameter? param = prop.get_first_parameter(iCal.icalparameter_kind.TZID_PARAMETER);
if (param != null)
zone = new Calendar.OlsonZone(param.get_tzid());
+
+ kind = ical_prop_kind;
}
/**
@@ -186,6 +211,21 @@ public class DateTime : BaseObject {
exact_time_span = null;
}
+ public int compare_to(Component.DateTime other) {
+ return (this != other) ? iCal.icaltime_compare(dt, other.dt) : 0;
+ }
+
+ public bool equal_to(Component.DateTime other) {
+ return (this != other) ? iCal.icaltime_compare(dt, other.dt) == 0 : true;
+ }
+
+ public uint hash() {
+ // iCal doesn't supply a hashing function, so here goes
+ iCal.icaltimetype utc = iCal.icaltime_convert_to_zone(dt, iCal.icaltimezone.get_utc_timezone());
+
+ return Memory.hash(&utc, sizeof(iCal.icaltimetype));
+ }
+
public override string to_string() {
try {
if (is_date)
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 7517437..334227c 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -12,7 +12,7 @@ namespace California.Component {
* See [[https://tools.ietf.org/html/rfc5545#section-3.6.1]]
*/
-public class Event : Instance, Gee.Comparable<Event> {
+public class Event : Instance, Sequenceable, Recurrable, Gee.Comparable<Event> {
public const string PROP_SUMMARY = "summary";
public const string PROP_DESCRIPTION = "description";
public const string PROP_EXACT_TIME_SPAN = "exact-time-span";
@@ -55,6 +55,16 @@ public class Event : Instance, Gee.Comparable<Event> {
public bool is_all_day { get; private set; }
/**
+ * For { link Recurrable}.
+ */
+ public Component.DateTime? rid { get; set; default = null; }
+
+ /**
+ * For { link Sequenceable}.
+ */
+ public int sequence { get; set; default = 0; }
+
+ /**
* Create an { link Event} { link Component} from an EDS CalComponent object.
*
* Throws a BackingError if the E.CalComponent's VTYPE is not VEVENT.
@@ -241,12 +251,66 @@ public class Event : Instance, Gee.Comparable<Event> {
if (compare != 0)
return compare;
+ // if recurring, go by sequence number, as the UID and RID are the same for all instances
+ if (is_recurring) {
+ compare = sequence - other.sequence;
+ if (compare != 0)
+ return compare;
+ }
+
// stabilize with UIDs
return uid.compare_to(other.uid);
}
+ public override bool equal_to(Component.Instance other) {
+ Component.Event? other_event = other as Component.Event;
+ if (other_event == null)
+ return false;
+
+ if (this == other_event)
+ return true;
+
+ if (is_recurring != other_event.is_recurring)
+ return false;
+
+ if (is_recurring && !rid.equal_to(other_event.rid))
+ return false;
+
+ if (sequence != other_event.sequence)
+ return false;
+
+ if (exact_time_span != null && other_event.exact_time_span != null
+ && !exact_time_span.equal_to(other_event.exact_time_span)) {
+ return false;
+ }
+
+ if (date_span != null && other_event.date_span != null
+ && !date_span.equal_to(other_event.date_span)) {
+ return false;
+ }
+
+ if (exact_time_span != null
+ && !new Calendar.DateSpan.from_exact_time_span(exact_time_span).equal_to(other_event.date_span))
{
+ return false;
+ }
+
+ if (!date_span.equal_to(new Calendar.DateSpan.from_exact_time_span(other_event.exact_time_span))) {
+ return false;
+ }
+
+ return base.equal_to(other);
+ }
+
+ public override uint hash() {
+ return uid.hash() ^ ((rid != null) ? rid.hash() : 0) ^ sequence;
+ }
+
public override string to_string() {
- return "Event \"%s\" (%s)".printf(summary,
+ return "Event %s/rid=%s/%d \"%s\" (%s)".printf(
+ uid.to_string(),
+ (rid != null) ? rid.to_string() : "(no-recurring)",
+ sequence,
+ summary,
exact_time_span != null ? exact_time_span.to_string() : date_span.to_string());
}
}
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 307af70..6226fce 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -9,19 +9,16 @@ namespace California.Component {
/**
* A mutable iCalendar component that has a definitive instance within a calendar.
*
- * By "instance", this means { link Event}s, To-Do's, Journals, and Free/Busy components. In other
- * words, components which allocate a specific item within a calendar. Some of thse
- * components may be recurring, in which case any particular instance is merely a generated
- * representation of that recurrance.
+ * By "instance", this means { link Event}s, To-Do's, and Journals, and Free/Busy components. In
+ * other words, components which allocate a specific amount of time within a calendar.
*
* Mutability is achieved two separate ways. One is to call { link full_update} supplying a new
- * iCal component to update an existing one (verified by UID and RID). This will update all
- * fields.
+ * iCal component to update an existing one (verified by UID). This will update all fields.
*
* The second is to update the mutable properties themselves, which will then update the underlying
* iCal component.
*
- * Alarms are contained within Instance components. Timezones are handled separately.
+ * Alarms will be contained within Instance components. Timezones are handled separately.
*
* Instance also offers a number of methods to convert iCal structures into internal objects.
*/
@@ -110,6 +107,9 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
uid = new UID(_ical_component.get_uid());
full_update(_ical_component);
+
+ // watch for property changes and update ical_component when happens
+ notify.connect(on_notify);
}
/**
@@ -121,6 +121,8 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
_ical_component = new iCal.icalcomponent(kind);
uid = Component.UID.generate();
_ical_component.set_uid(uid.value);
+
+ notify.connect(on_notify);
}
/**
@@ -187,10 +189,49 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
if (!dt_stamp.is_date)
dtstamp = dt_stamp.to_exact_time();
+ //
+ // Interface hooks
+ //
+
+ Recurrable? recurrable = this as Recurrable;
+ if (recurrable != null)
+ recurrable.update_from_component(ical_component);
+
+ Sequenceable? sequenceable = this as Sequenceable;
+ if (sequenceable != null)
+ sequenceable.update_from_component(ical_component);
+
+ // save own copy of component; no ownership transferrance w/ current bindings
if (_ical_component != ical_component)
_ical_component = ical_component.clone();
}
+ private void on_notify(ParamSpec pspec) {
+ // don't worry if in full update, that call is supposed to update properties
+ if (in_full_update)
+ return;
+
+ bool altered = false;
+
+ //
+ // Interface hooks
+ //
+
+ Recurrable? recurrable = this as Recurrable;
+ if (recurrable != null)
+ altered = recurrable.on_notify(pspec) || altered;
+
+ Sequenceable? sequenceable = this as Sequenceable;
+ if (sequenceable != null)
+ altered = sequenceable.on_notify(pspec) || altered;
+
+ // Instance currently has no properties of its own which may be updated except dtstamp,
+ // which is a special case
+
+ if (altered)
+ notify_altered(false);
+ }
+
/**
* Returns an appropriate { link Component} instance for the iCalendar component.
*
@@ -213,7 +254,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* Convenience method to convert a { link Calendar.Date} to an iCal DATE.
*/
- protected static void date_to_ical(Calendar.Date date, iCal.icaltimetype *ical_dt) {
+ internal static void date_to_ical(Calendar.Date date, iCal.icaltimetype *ical_dt) {
ical_dt->year = date.year.value;
ical_dt->month = date.month.value;
ical_dt->day = date.day_of_month.value;
@@ -233,7 +274,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
* of the span. See the iCal specification for information on how each component should
* treat the situation.
*/
- protected static void date_span_to_ical(Calendar.DateSpan date_span, bool dtend_inclusive,
+ internal static void date_span_to_ical(Calendar.DateSpan date_span, bool dtend_inclusive,
iCal.icaltimetype *ical_dtstart, iCal.icaltimetype *ical_dtend) {
date_to_ical(date_span.start_date, ical_dtstart);
date_to_ical(date_span.end_date.adjust(dtend_inclusive ? 0 : 1, Calendar.DateUnit.DAY),
@@ -243,7 +284,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* Convenience method to convert a { link Calendar.ExactTime} to an iCal DATE-TIME.
*/
- protected static void exact_time_to_ical(Calendar.ExactTime exact_time, iCal.icaltimetype *ical_dt) {
+ internal static void exact_time_to_ical(Calendar.ExactTime exact_time, iCal.icaltimetype *ical_dt) {
ical_dt->year = exact_time.year.value;
ical_dt->month = exact_time.month.value;
ical_dt->day = exact_time.day_of_month.value;
@@ -261,21 +302,43 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* Convenience method to convert a { link Calendar.ExactTimeSpan} to a pair of iCal DATE-TIMEs.
*/
- protected static void exact_time_span_to_ical(Calendar.ExactTimeSpan exact_time_span,
+ internal static void exact_time_span_to_ical(Calendar.ExactTimeSpan exact_time_span,
iCal.icaltimetype *ical_dtstart, iCal.icaltimetype *ical_dtend) {
exact_time_to_ical(exact_time_span.start_exact_time, ical_dtstart);
exact_time_to_ical(exact_time_span.end_exact_time, ical_dtend);
}
/**
- * Equality is defined as { link Component.Instance}s having the same UID (and, when available,
- * RID), nothing more.
+ * Convenience method to remove all instances of a property from { link ical_component}.
+ *
+ * @returns The number of properties found with the specified kind.
+ */
+ internal int remove_all_properties(iCal.icalproperty_kind kind) {
+ int count = 0;
+ unowned iCal.icalproperty? prop;
+ while ((prop = ical_component.get_first_property(kind)) != null) {
+ ical_component.remove_property(prop);
+ count++;
+ }
+
+ return count;
+ }
+
+ /**
+ * Equality is defined as { link Component.Instance}s having the same UID.
+ *
+ * Subclasses should override this and { link hash} if more definite equality is necessary.
*/
- public bool equal_to(Instance other) {
+ public virtual bool equal_to(Instance other) {
return (this != other) ? uid.equal_to(other.uid) : true;
}
- public uint hash() {
+ /**
+ * Hash is calculated using the { link Instance} { link UID}.
+ *
+ * Subclasses should override if they override { link equal_to}.
+ */
+ public virtual uint hash() {
return uid.hash();
}
}
diff --git a/src/component/component-recurrable.vala b/src/component/component-recurrable.vala
new file mode 100644
index 0000000..a8ad3e5
--- /dev/null
+++ b/src/component/component-recurrable.vala
@@ -0,0 +1,67 @@
+/* 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 {
+
+/**
+ * Interface for iCal components which potentially can be configured as recurring within a
+ * calendar.
+ *
+ * iCal defines the components which support recurrances as VEVENT, VTODO, and VJOURNAL.
+ */
+
+public interface Recurrable : Component.Instance, Component.Sequenceable {
+ public const string PROP_RID = "rid";
+
+ private const iCal.icalproperty_kind KIND_RID = iCal.icalproperty_kind.RECURRENCEID_PROPERTY;
+
+ /**
+ * The RECURRENCE-ID of a recurring component.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.4.4]]
+ */
+ public abstract Component.DateTime? rid { get; set; default = null; }
+
+ /**
+ * Returns true if the { link Recurrable} is in fact a recurring instance.
+ */
+ public bool is_recurring { get { return rid != null; } }
+
+ // Called from within Component.Instance
+ internal void update_from_component(iCal.icalcomponent ical_component) throws Error {
+ try {
+ rid = new DateTime(ical_component, KIND_RID);
+ } catch (ComponentError comperr) {
+ // ignore if unavailable
+ if (!(comperr is ComponentError.UNAVAILABLE))
+ throw comperr;
+
+ rid = null;
+ }
+ }
+
+ // Called from within Component.Instance
+ internal bool on_notify(ParamSpec pspec) {
+ bool altered = true;
+ switch (pspec.name) {
+ case PROP_RID:
+ if (rid == null)
+ remove_all_properties(KIND_RID);
+ else
+ ical_component.set_recurrenceid(rid.dt);
+ break;
+
+ default:
+ altered = false;
+ break;
+ }
+
+ return altered;
+ }
+}
+
+}
+
diff --git a/src/component/component-sequenceable.vala b/src/component/component-sequenceable.vala
new file mode 100644
index 0000000..c663d57
--- /dev/null
+++ b/src/component/component-sequenceable.vala
@@ -0,0 +1,48 @@
+/* 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 {
+
+/**
+ * Interface for iCal components which track their revisions via a counter.
+ *
+ * iCal defines the components which support sequencing as VEVENT, VTODO, and VJOURNAL.
+ */
+
+public interface Sequenceable : Component.Instance {
+ public const string PROP_SEQUENCE = "sequence";
+
+ /**
+ * The SEQUENCE of a VEVENT, VTODO, or VJOURNAL.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.8.7.4]]
+ */
+ public abstract int sequence { get; set; default = 0; }
+
+ // Called from within Component.Instance
+ internal void update_from_component(iCal.icalcomponent ical_component) throws Error {
+ sequence = ical_component.get_sequence();
+ }
+
+ // Called from within Component.Instance
+ internal bool on_notify(ParamSpec pspec) {
+ bool altered = true;
+ switch (pspec.name) {
+ case PROP_SEQUENCE:
+ ical_component.set_sequence(sequence);
+ break;
+
+ default:
+ altered = false;
+ break;
+ }
+
+ return altered;
+ }
+}
+
+}
+
diff --git a/src/util/util-memory.vala b/src/util/util-memory.vala
new file mode 100644
index 0000000..93981f6
--- /dev/null
+++ b/src/util/util-memory.vala
@@ -0,0 +1,27 @@
+/* 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.Memory {
+
+/**
+ * A rotating-XOR hash that can be used to hash memory buffers of any size.
+ */
+public uint hash(void *ptr, size_t bytes) {
+ if (bytes == 0)
+ return 0;
+
+ uint8 *u8 = (uint8 *) ptr;
+
+ // initialize hash to first byte value and then rotate-XOR from there
+ uint hash = *u8;
+ for (int ctr = 1; ctr < bytes; ctr++)
+ hash = (hash << 4) ^ (hash >> 28) ^ (*u8++);
+
+ return hash;
+}
+
+}
+
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index 5c7ed5a..b22b2d0 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -266,10 +266,10 @@ public class Controllable : Gtk.Grid, View.Controllable {
Backing.CalendarSourceSubscription subscription = calendar.subscribe_async.end(result);
subscriptions.add(subscription);
- subscription.event_discovered.connect(on_event_added);
- subscription.event_added.connect(on_event_added);
- subscription.event_removed.connect(on_event_removed);
- subscription.event_dropped.connect(on_event_removed);
+ subscription.instance_discovered.connect(on_instance_added);
+ subscription.instance_added.connect(on_instance_added);
+ subscription.instance_removed.connect(on_instance_removed);
+ subscription.instance_dropped.connect(on_instance_removed);
// this will start signals firing for event changes
subscription.start();
@@ -278,7 +278,11 @@ public class Controllable : Gtk.Grid, View.Controllable {
}
}
- private void on_event_added(Component.Event event) {
+ private void on_instance_added(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
// add event to every date it represents
foreach (Calendar.Date date in event.get_event_date_span()) {
Cell? cell = date_to_cell.get(date);
@@ -287,7 +291,11 @@ public class Controllable : Gtk.Grid, View.Controllable {
}
}
- private void on_event_removed(Component.Event event) {
+ private void on_instance_removed(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
foreach (Calendar.Date date in event.get_event_date_span()) {
Cell? cell = date_to_cell.get(date);
if (cell != null)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]