# HG changeset patch
# Parent d02845b002ab4314f2873a4bf2265cf370228396
WeekPlanPlugin: Define IWeekPlanEventProvider extension point. (fix #11615)

diff -r d02845b002ab weekplan/core.py
--- a/weekplan/core.py	Thu Aug 14 10:58:45 2014 +0200
+++ b/weekplan/core.py	Sat Sep 06 16:31:17 2014 +0200
@@ -8,6 +8,7 @@
 
 from trac import __version__
 from trac.db.api import DatabaseManager
+from trac.config import OrderedExtensionsOption, ExtensionOption
 from trac.core import *
 from trac.env import IEnvironmentSetupParticipant
 from trac.perm import IPermissionRequestor
@@ -23,11 +24,111 @@
 PLUGIN_VERSION = 2
 
 
+class IWeekPlanEventProvider(Interface):
+    
+    def get_plan_prefixes():
+        """Get all plan id prefixes handled by this provider.
+        
+        :return: a list of plan id prefix strings.
+        """
+    
+    def get_events(plans_ids, range):
+        """Get all events for the given plans that the provider handles.
+
+        :param plan_ids: a list of plan id's for which events should be returned.
+        :param range: a tuple (datetime, datetime) or None to get all events.
+        
+        :return: a list of  WeekPlanEvent items.
+        """
+
+    def add_event(event):
+        """Add a new event (if the provider handles this event).
+        
+        :param event: a new WeekPlanEvent to be added.
+        """
+
+    def update_event(event):
+        """Update an event (if the provider handles this event).
+        
+        :param event: a WeekPlanEvent to be updated.
+        """
+
+    def delete_event(event):
+        """Delete an event (if the provider handles this event).
+        
+        :param event: a WeekPlanEvent to be deleted.
+        """
+
+
+class DBWeekPlanEventProvider(Component):
+    """Provides events from the dedicated DB table."""
+    
+    implements(IWeekPlanEventProvider)
+    
+    def get_plan_prefixes(self):
+        return ['db:']
+    
+    def get_events(self, plan_ids, range):
+        if range:
+            return WeekPlanEvent.select_by_plans_and_time(self.env, plan_ids, range[0], range[1])
+        elif len(plan_ids) == 1:
+            return WeekPlanEvent.select_by_plan(self.env, plan_ids[0])
+        elif db_plan_ids:
+            return WeekPlanEvent.select_by_plans(self.env, plan_ids)
+        else:
+            return []
+
+    def add_event(self, event):
+        WeekPlanEvent.add(self.env, event)
+
+    def update_event(self, event):
+        WeekPlanEvent.update(self.env, event)
+
+    def delete_event(self, event):
+        WeekPlanEvent.delete_by_id(self.env, event.id)
+
+
 class WeekPlanModule(Component):
     """Week-by-week plans."""
 
     implements(IPermissionRequestor, IRequestHandler, IEnvironmentSetupParticipant, ITemplateProvider)
 
+    event_providers = OrderedExtensionsOption('weekplan', 'event_providers',
+        IWeekPlanEventProvider,
+        'DBWeekPlanEventProvider',
+        True,
+        """List of components implementing `IWeekPlanEventProvider`.""")
+
+    default_event_provider = ExtensionOption('weekplan', 'default_event_provider',
+        IWeekPlanEventProvider,
+        'DBWeekPlanEventProvider',
+        """Default component handling plans if no prefix matches.""")
+
+
+    def get_event_provider(self, plan_id):
+        for provider in self.event_providers:
+            for prefix in provider.get_plan_prefixes():
+                if plan_id.startswith(prefix):
+                    return provider
+        return self.default_event_provider
+
+    def get_events(self, plan_ids, range=None):
+        events = []
+        remaining_ids = set(plan_ids)
+        for provider in self.event_providers:
+            prefixes = provider.get_plan_prefixes()
+            ids = [id for id in remaining_ids
+                      for prefix in prefixes
+                      if id.startswith(prefix)]
+            remaining_ids -= set(ids)
+            if ids:
+                events.extend(provider.get_events(ids, range))
+        if remaining_ids:
+            provider = self.default_event_provider
+            ids = list(remaining_ids)
+            events.extend(provider.get_events(ids, range))
+        return events
+
     # IPermissionRequestor methods
     
     def get_permission_actions(self):
@@ -52,29 +153,33 @@
         if 'POST' == req.method:
             if action == 'add_event':
                 event = self._parse_event(req)
-                WeekPlanEvent.add(self.env, event)
+                provider = self.get_event_provider(event.plan)
+                provider.add_event(event)
                 self._send_event(req, event)
 
             elif action == 'update_event':
                 event = self._parse_event(req)
-                WeekPlanEvent.update(self.env, event)
+                provider = self.get_event_provider(event.plan)
+                provider.update_event(event)
                 self._send_event(req, event)
 
             elif action == 'delete_event':
-                event_id = req.args.get('id')
-                WeekPlanEvent.delete_by_id(self.env, event_id)
+                event = self._parse_event(req)
+                provider = self.get_event_provider(event.plan)
+                provider.delete_event(event)
                 req.send_no_content()
+
         elif 'GET' == req.method:
             format = req.args.get('format')
             if format == 'ics':
                 plan_id = req.args.get('plan')
-                events = WeekPlanEvent.select_by_plan(self.env, plan_id)
+                events = self.get_events([plan_id])
                 self._render_ics(req, plan_id, events)
             elif format == 'json':
                 plan_ids = req.args.get('plan', '').split('|')
                 start = parse_date(req.args.get('start'))
                 end = parse_date(req.args.get('end'))
-                events = WeekPlanEvent.select_by_plans_and_time(self.env, plan_ids, start, end)
+                events = self.get_events(plan_ids, (start, end))
                 self._send_events(req, events)
         raise TracError()
 
