[california/wip/727120-webcal] Use CalendarSubscriptionManager to deal with mass subscriptions



commit e15f1da83130113cfacd2f08f1c6bd77e380f594
Author: Jim Nelson <jim yorba org>
Date:   Thu Mar 27 17:56:35 2014 -0700

    Use CalendarSubscriptionManager to deal with mass subscriptions
    
    This fixes a couple of bugs in handling calendars from coming and
    going.

 src/Makefile.am                                    |    1 +
 .../backing-calendar-source-subscription.vala      |    1 +
 .../backing-calendar-subscription-manager.vala     |  176 ++++++++++++++++++++
 src/backing/backing-store.vala                     |    4 +-
 src/backing/eds/backing-eds-store.vala             |    4 +-
 src/component/component-instance.vala              |    2 +-
 src/view/month/month-controllable.vala             |   46 ++---
 7 files changed, 201 insertions(+), 33 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 93d896c..9f3fbca 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -16,6 +16,7 @@ california_VALASOURCES = \
        backing/backing-activator.vala \
        backing/backing-calendar-source.vala \
        backing/backing-calendar-source-subscription.vala \
+       backing/backing-calendar-subscription-manager.vala \
        backing/backing-error.vala \
        backing/backing-manager.vala \
        backing/backing-source.vala \
diff --git a/src/backing/backing-calendar-source-subscription.vala 
b/src/backing/backing-calendar-source-subscription.vala
index 90245d5..b54fea9 100644
--- a/src/backing/backing-calendar-source-subscription.vala
+++ b/src/backing/backing-calendar-source-subscription.vala
@@ -255,6 +255,7 @@ public abstract class CalendarSourceSubscription : BaseObject {
         
         // Use to_array() so no iteration troubles when notify_instance_dropped removes it from
         // the multimap
+        debug("Dropping %d instances to %s: unavailable", instances.size, calendar.to_string());
         foreach (Component.Instance instance in instances.get_values().to_array())
             notify_instance_dropped(instance);
     }
diff --git a/src/backing/backing-calendar-subscription-manager.vala 
b/src/backing/backing-calendar-subscription-manager.vala
new file mode 100644
index 0000000..c4851ef
--- /dev/null
+++ b/src/backing/backing-calendar-subscription-manager.vala
@@ -0,0 +1,176 @@
+/* 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.Backing {
+
+/**
+ * Subscribe to all { link CalendarSource}s and their { link Component.Instance}s for a specific
+ * span of time.
+ *
+ * This class manages the signals and { link CalendarSourceSubscription}s for all registered
+ * calendars and converts their important events into a set of symmetric signals.  It also
+ * automatically subscribes to new calendars (and unsubscribes to dropped ones) notifying of changes
+ * through the same set of signals  Callers should simply subscribe to those signals and let them
+ * drive the application.
+ *
+ * The time span { link window} cannot be altered once the object is created.
+ */
+
+public class CalendarSubscriptionManager : BaseObject {
+    /**
+     * The time span for all managed subscriptions.
+     */
+    public Calendar.ExactTimeSpan window { get; private set; }
+    
+    /**
+     * Indicates a { link CalendarSource} was added to the manager, either listed when first
+     * created or detected at runtime afterwards.
+     */
+    public signal void calendar_added(Backing.CalendarSource calendar);
+    
+    /**
+     * Indicates the { link CalendarSource} was removed from the manager.
+     */
+    public signal void calendar_removed(Backing.CalendarSource calendar);
+    
+    /**
+     * Indicates the { link Component.Instance} was generated by one of the managed subscriptions,
+     * either generated (discovered) when first opened or added later.
+     */
+    public signal void instance_added(Component.Instance instance);
+    
+    /**
+     * Indicates the { link Component.Instance} was removed by one of the managed subscriptions,
+     * either due to the { link CalendarSource} being made unavailable or removal by the user.
+     */
+    public signal void instance_removed(Component.Instance instance);
+    
+    /**
+     * An error was returned when attempting to subscribe to the { link CalendarSource}.
+     */
+    public signal void subscription_error(Backing.CalendarSource calendar, Error err);
+    
+    private Gee.ArrayList<Backing.CalendarSourceSubscription> subscriptions = new Gee.ArrayList<
+        Backing.CalendarSourceSubscription>();
+    private Cancellable cancellable = new Cancellable();
+    
+    /**
+     * Create a new { link CalendarSubscriptionManager}.
+     *
+     * The { link window} cannot be modified once created.
+     *
+     * Events will not be signalled until { link start} is called.
+     */
+    public CalendarSubscriptionManager(Calendar.ExactTimeSpan window) {
+        this.window = window;
+    }
+    
+    ~CalendarSubscriptionManager() {
+        // cancel any outstanding subscription starts
+        cancellable.cancel();
+        
+        // drop signals on objects that will persist after this object's destruction
+        foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
+            store.source_added.disconnect(on_source_added);
+            store.source_removed.disconnect(on_source_removed);
+        }
+    }
+    
+    /**
+     * Generate subscriptions and begin firing signals.
+     *
+     * There is no "stop" method.  Destroying the object will cancel all subscriptions, although
+     * signals will not be fired at that time.
+     */
+    public void start() {
+        foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
+            // watch each store for future added sources
+            store.source_added.connect(on_source_added);
+            store.source_removed.connect(on_source_removed);
+            
+            foreach (Backing.Source source in store.get_sources_of_type<Backing.CalendarSource>())
+                add_calendar((Backing.CalendarSource) source);
+        }
+    }
+    
+    private void on_source_added(Backing.Source source) {
+        Backing.CalendarSource? calendar = source as Backing.CalendarSource;
+        if (calendar != null)
+            add_calendar(calendar);
+    }
+    
+    private void add_calendar(Backing.CalendarSource calendar) {
+        // report calendar as added to subscription
+        calendar_added(calendar);
+        
+        // start generating instances on this calendar
+        calendar.subscribe_async.begin(window, cancellable, on_subscribed);
+    }
+    
+    // Since this might be called after the dtor has finished (cancelling the operation), don't
+    // touch the "this" ref unless the Error is shown not to be a cancellation
+    private void on_subscribed(Object? source, AsyncResult result) {
+        Backing.CalendarSource calendar = (Backing.CalendarSource) source;
+        
+        try {
+            Backing.CalendarSourceSubscription subscription = calendar.subscribe_async.end(result);
+            
+            // okay to use "this" ref
+            subscriptions.add(subscription);
+            
+            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);
+            subscription.start_failed.connect(on_error);
+            
+            // this will start signals firing for event changes
+            subscription.start();
+        } catch (Error err) {
+            debug("Unable to subscribe to %s: %s", calendar.to_string(), err.message);
+            
+            // only fire -- or even touch "this" -- if not a cancellation
+            if (!(err is IOError.CANCELLED))
+                subscription_error(calendar, err);
+        }
+    }
+    
+    private void on_instance_added(Component.Instance instance) {
+        instance_added(instance);
+    }
+    
+    private void on_instance_removed(Component.Instance instance) {
+        instance_removed(instance);
+    }
+    
+    private void on_error(CalendarSourceSubscription subscription, Error err) {
+        subscription_error(subscription.calendar, err);
+    }
+    
+    // Don't need to do much here as all instances are dropped prior to the source being removed
+    private void on_source_removed(Backing.Source source) {
+        Backing.CalendarSource? calendar = source as Backing.CalendarSource;
+        if (calendar == null)
+            return;
+        
+        // drop all related subscriptions ... their instances should've been dropped via the
+        // "instance-dropped" signal, so no signal their removal here
+        Gee.Iterator<CalendarSourceSubscription> iter = subscriptions.iterator();
+        while (iter.next()) {
+            if (iter.get().calendar == calendar)
+                iter.remove();
+        }
+        
+        calendar_removed(calendar);
+    }
+    
+    public override string to_string() {
+        return "%s window=%s".printf(get_class().get_type().name(), window.to_string());
+    }
+}
+
+}
+
diff --git a/src/backing/backing-store.vala b/src/backing/backing-store.vala
index 46bfb29..527b0a2 100644
--- a/src/backing/backing-store.vala
+++ b/src/backing/backing-store.vala
@@ -29,7 +29,7 @@ public abstract class Store : BaseObject {
      *
      * Also fired in { link open_async} when Sources are discovered.
      */
-    public virtual signal void added(Source source) {
+    public virtual signal void source_added(Source source) {
         debug("%s: added %s", to_string(), source.to_string());
     }
     
@@ -40,7 +40,7 @@ public abstract class Store : BaseObject {
      *
      * Also called in { link close_async} when internal refs are being dropped.
      */
-    public virtual signal void removed(Source source) {
+    public virtual signal void source_removed(Source source) {
         debug("%s: removed %s", to_string(), source.to_string());
     }
     
diff --git a/src/backing/eds/backing-eds-store.vala b/src/backing/eds/backing-eds-store.vala
index 776b3a7..11f72bf 100644
--- a/src/backing/eds/backing-eds-store.vala
+++ b/src/backing/eds/backing-eds-store.vala
@@ -130,7 +130,7 @@ internal class EdsStore : Store, WebCalSubscribable {
         
         sources.set(eds_source, calendar);
         
-        added(calendar);
+        source_added(calendar);
     }
     
     // since the registry ref may have been dropped (in close_async), it shouldn't be ref'd here
@@ -140,7 +140,7 @@ internal class EdsStore : Store, WebCalSubscribable {
         if (sources.unset(eds_source, out source)) {
             assert(source != null);
             
-            removed(source);
+            source_removed(source);
             source.set_unavailable();
         }
         
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index f9769d7..8124f90 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -39,7 +39,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      *
      * This will initialize as null if created as a { link blank} Instance.
      */
-    public Backing.CalendarSource? calendar_source { get; set; default = null; }
+    public unowned Backing.CalendarSource? calendar_source { get; set; default = null; }
     
     /**
      * The date-time stamp of the { link Instance}.
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index 6528522..3707df8 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -65,8 +65,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
     public Calendar.Date default_date { get; protected set; }
     
     private Gee.HashMap<Calendar.Date, Cell> date_to_cell = new Gee.HashMap<Calendar.Date, Cell>();
-    private Gee.ArrayList<Backing.CalendarSourceSubscription> subscriptions = new Gee.ArrayList<
-        Backing.CalendarSourceSubscription>();
+    private Backing.CalendarSubscriptionManager? subscriptions = null;
     private Gdk.EventType button_press_type = Gdk.EventType.NOTHING;
     private Gdk.Point button_press_point = Gdk.Point();
     
@@ -275,34 +274,25 @@ public class Controllable : Gtk.Grid, View.Controllable {
         Calendar.ExactTimeSpan time_window = new Calendar.ExactTimeSpan.from_date_span(window,
             Calendar.Timezone.local);
         
-        // clear current subscriptions and generate new subscriptions for new window
-        subscriptions.clear();
-        foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
-            foreach (Backing.Source source in store.get_sources_of_type<Backing.CalendarSource>()) {
-                Backing.CalendarSource calendar = (Backing.CalendarSource) source;
-                calendar.notify[Backing.Source.PROP_VISIBLE].connect(queue_draw);
-                calendar.subscribe_async.begin(time_window, null, on_subscribed);
-            }
-        }
+        // create new subscription manager, subscribe to its signals, and let them drive
+        subscriptions = null;
+        subscriptions = new Backing.CalendarSubscriptionManager(time_window);
+        subscriptions.calendar_added.connect(on_calendar_added);
+        subscriptions.calendar_removed.connect(on_calendar_removed);
+        subscriptions.instance_added.connect(on_instance_added);
+        subscriptions.instance_removed.connect(on_instance_removed);
+        
+        subscriptions.start();
     }
     
-    private void on_subscribed(Object? source, AsyncResult result) {
-        Backing.CalendarSource calendar = (Backing.CalendarSource) source;
-        
-        try {
-            Backing.CalendarSourceSubscription subscription = calendar.subscribe_async.end(result);
-            subscriptions.add(subscription);
-            
-            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();
-        } catch (Error err) {
-            debug("Unable to subscribe to %s: %s", calendar.to_string(), err.message);
-        }
+    private void on_calendar_added(Backing.CalendarSource calendar) {
+        calendar.notify[Backing.Source.PROP_VISIBLE].connect(queue_draw);
+        calendar.notify[Backing.Source.PROP_COLOR].connect(queue_draw);
+    }
+    
+    private void on_calendar_removed(Backing.CalendarSource calendar) {
+        calendar.notify[Backing.Source.PROP_VISIBLE].disconnect(queue_draw);
+        calendar.notify[Backing.Source.PROP_COLOR].disconnect(queue_draw);
     }
     
     private void on_instance_added(Component.Instance instance) {


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