wingo - in flumotion/trunk: . flumotion/component/base
flumotion/test
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Mon May 14 17:16:39 CEST 2007
Author: wingo
Date: Mon May 14 17:16:36 2007
New Revision: 4940
Added:
flumotion/trunk/flumotion/component/base/scheduler.py
flumotion/trunk/flumotion/test/test_component_base_scheduler.py
Modified:
flumotion/trunk/ChangeLog
flumotion/trunk/flumotion/component/base/Makefile.am
flumotion/trunk/flumotion/component/base/base.xml
flumotion/trunk/flumotion/test/Makefile.am
Log:
2007-05-14 Andy Wingo <wingo at pobox.com>
* flumotion/component/base/Makefile.am (component_PYTHON):
* flumotion/component/base/base.xml:
* flumotion/component/base/scheduler.py (Event, Scheduler)
(ICalScheduler): New classes, meant to replace the ical things in
the icalbouncer and in the disker.
* flumotion/test/Makefile.am (EXTRA_DIST):
* flumotion/test/test_component_base_scheduler.py (SchedulerTest):
Very minimal test.
Modified: flumotion/trunk/ChangeLog
==============================================================================
--- flumotion/trunk/ChangeLog (original)
+++ flumotion/trunk/ChangeLog Mon May 14 17:16:36 2007
@@ -1,3 +1,15 @@
+2007-05-14 Andy Wingo <wingo at pobox.com>
+
+ * flumotion/component/base/Makefile.am (component_PYTHON):
+ * flumotion/component/base/base.xml:
+ * flumotion/component/base/scheduler.py (Event, Scheduler)
+ (ICalScheduler): New classes, meant to replace the ical things in
+ the icalbouncer and in the disker.
+
+ * flumotion/test/Makefile.am (EXTRA_DIST):
+ * flumotion/test/test_component_base_scheduler.py (SchedulerTest):
+ Very minimal test.
+
2007-05-14 Zaheer Abbas Merali <<zaheerabbas at merali dot org>>
* flumotion/component/combiners/switch/switch.py
Modified: flumotion/trunk/flumotion/component/base/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/component/base/Makefile.am (original)
+++ flumotion/trunk/flumotion/component/base/Makefile.am Mon May 14 17:16:36 2007
@@ -1,6 +1,6 @@
include $(top_srcdir)/common/python.mk
-
-component_PYTHON = __init__.py admin_gtk.py admin_text.py http.py
+
+component_PYTHON = __init__.py admin_gtk.py admin_text.py http.py scheduler.py
componentdir = $(libdir)/flumotion/python/flumotion/component/base
component_DATA = base.xml feeders.glade eaters.glade
Modified: flumotion/trunk/flumotion/component/base/base.xml
==============================================================================
--- flumotion/trunk/flumotion/component/base/base.xml (original)
+++ flumotion/trunk/flumotion/component/base/base.xml Mon May 14 17:16:36 2007
@@ -35,5 +35,15 @@
</directory>
</directories>
</bundle>
+ <bundle name="base-scheduler">
+ <dependencies>
+ <dependency name="flumotion" />
+ </dependencies>
+ <directories>
+ <directory name="flumotion/component/base">
+ <filename location="scheduler.py" />
+ </directory>
+ </directories>
+ </bundle>
</bundles>
</registry>
Added: flumotion/trunk/flumotion/component/base/scheduler.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/component/base/scheduler.py Mon May 14 17:16:36 2007
@@ -0,0 +1,267 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+
+from datetime import datetime
+
+from twisted.internet import reactor
+
+from flumotion.common import log, avltree
+
+
+class Event(log.Loggable):
+ """
+ I am an event. I have a start and stop time and a "content" that can
+ be anything. I can recur.
+ """
+
+ def __init__(self, start, end, content, recur=None, now=None):
+ from dateutil import rrule
+ self._rrulestr = rrule.rrulestr
+
+ self.debug('new event, content=%r, start=%r, end=%r', start,
+ end, content)
+ self.setBounds(recur, start, end, now or datetime.now())
+ self.content = content
+ self.recur = recur
+
+ def setBounds(self, recur, start, end, now):
+ self.recur = recur
+ if recur:
+ startRecurRule = self._rrulestr(recur, dtstart=start)
+ endRecurRule = self._rrulestr(recur, dtstart=end)
+ if end < now:
+ end = endRecurRule.after(now)
+ start = startRecurRule.before(end)
+ self.debug("adjusting start and end times to %r, %r",
+ start, end)
+ self.start, self.end = start, end
+
+ def reschedule(self):
+ if self.recur:
+ return Event(self.start, self.end, self.content, self.recur)
+ else:
+ return None
+
+ def __lt__(self, other):
+ return self.start < other.start
+
+ def __gt__(self, other):
+ return self.start > other.start
+
+ def __eq__(self, other):
+ return self.start == other.start
+
+
+class Scheduler(log.Loggable):
+ """
+ I keep track of upcoming events.
+
+ I can provide notifications when events stop and start, and maintain
+ a set of current events.
+ """
+
+ def __init__(self):
+ self.current = []
+ self._delayedCall = None
+ self._subscribeId = 0
+ self.subscribers = {}
+ self.replaceEvents([])
+
+ def addEvent(self, start, end, content, recur=None):
+ """Add a new event to the scheduler.
+
+ @param start: wall-clock time of event start
+ @type start: datetime
+ @param end: wall-clock time of event end
+ @type end: datetime
+ @param content: content of this event
+ @type content: str
+ @param recur: recurrence rule
+ @type recur: str
+
+ @returns: an Event that can later be passed to removeEvent, if
+ so desired. The event will be removed or rescheduled
+ automatically when it stops.
+ """
+ now = datetime.now()
+ event = Event(start, end, content, recur, now)
+ if event.end < now:
+ self.warning('attempted to schedule event in the past: %r',
+ event)
+ else:
+ self.events.insert(event)
+ if event.start < now:
+ self._eventStarted(event)
+ self._reschedule()
+ return event
+
+ def removeEvent(self, event):
+ """Remove an event from the scheduler.
+
+ @param event: an event, as returned from addEvent()
+ @type event: Event
+ """
+ currentEvent = event.reschedule() or event
+ self.events.delete(currentEvent)
+ if currentEvent in self.current:
+ self._eventStopped(currentEvent)
+ self._reschedule()
+
+ def replaceEvents(self, events):
+ """Replace the set of events in the scheduler.
+
+ This function is different than simply removing all events then
+ adding new ones, because it tries to avoid spurious
+ stopped/start notifications.
+
+ @param events: the new events
+ @type events: a sequence of Event
+ """
+ now = datetime.now()
+ self.events = avltree.AVLTree(events)
+ current = []
+ for event in self.events:
+ if now < event.start:
+ break
+ elif event.end < now:
+ # yay functional trees: we don't modify the iterator
+ self.events.delete(event)
+ else:
+ current.append(event)
+ for event in self.current[:]:
+ if event not in current:
+ self._eventStopped(event)
+ for event in current:
+ if event not in self.current:
+ self._eventStarted(event)
+ assert self.current == current
+ self._reschedule()
+
+ def subscribe(self, eventStarted, eventStopped):
+ """Subscribe to event happenings in the scheduler.
+
+ @param eventStarted: Function that will be called when an event
+ starts.
+ @type eventStarted: Event -> None
+ @param eventStopped: Function that will be called when an event
+ stops.
+ @type eventStopped: Event -> None
+
+ @returns: A subscription ID that can later be passed to
+ unsubscribe().
+ """
+ sid = self._subscribeId
+ self._subscribeId += 1
+ self.subscribers[sid] = (eventStarted, eventStopped)
+ return sid
+
+ def unsubscribe(self, id):
+ """Unsubscribe from event happenings in the scheduler.
+
+ @param id: Subscription ID received from subscribe()
+ """
+ del self.subscribers[id]
+
+ def _eventStarted(self, event):
+ self.current.append(event)
+ for started, _ in self.subscribers.values():
+ started(event.content)
+
+ def _eventStopped(self, event):
+ self.current.remove(event)
+ for _, stopped in self.subscribers.values():
+ stopped(event.content)
+
+ def _reschedule(self):
+ def _getNextStart():
+ for event in self.events:
+ if event not in self.current:
+ return event
+ return None
+
+ def _getNextStop():
+ t = None
+ e = None
+ for event in self.current:
+ if not t or event.end < t:
+ t = event.end
+ e = event
+ return e
+
+ def doStart(e):
+ self._eventStarted(e)
+ self._reschedule()
+
+ def doStop(e):
+ self._eventStopped(e)
+ self.events.delete(e)
+ new = e.reschedule()
+ if new:
+ self.events.insert(new)
+ self._reschedule()
+
+ if self._delayedCall:
+ self._delayedCall.cancel()
+ self._delayedCall = None
+
+ start = _getNextStart()
+ stop = _getNextStop()
+ now = datetime.now()
+
+ if start and (not stop or start.start < stop.end):
+ dc = reactor.callLater(max (start.start - now, 0), doStart,
+ start)
+ elif stop:
+ dc = reactor.callLater(max (stop.end - now, 0), doStop,
+ stop)
+ else:
+ dc = None
+
+ self._delayedCall = dc
+
+
+class ICalScheduler(Scheduler):
+ """
+ I am a scheduler that takes its data from an ical file.
+ """
+
+ def __init__(self, fileObj):
+ from icalendar import Calendar
+
+ Scheduler.__init__(self)
+ cal = Calendar.from_string(fileObj.read())
+ self.parseCalendar(cal)
+
+ def parseCalendar(self, cal):
+ for event in cal.walk('vevent'):
+ start = event.decoded('dtstart', None)
+ end = event.decoded('dtend', None)
+ summary = event.decoded('summary', None)
+ recur = event.get('rrule', None)
+ if start and end:
+ if recur:
+ self.addEvent(start, end, summary, recur.ical())
+ else:
+ self.addEvent(start, end, summary)
+ else:
+ self.warning('ical has event without start or end: '
+ '%r', event)
Modified: flumotion/trunk/flumotion/test/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/test/Makefile.am (original)
+++ flumotion/trunk/flumotion/test/Makefile.am Mon May 14 17:16:36 2007
@@ -27,6 +27,7 @@
test_common_planet.py \
test_common_pygobject.py \
test_component.py \
+ test_component_base_scheduler.py\
test_component_init.py \
test_component_feed.py \
test_component_httpserver.py \
Added: flumotion/trunk/flumotion/test/test_component_base_scheduler.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/test/test_component_base_scheduler.py Mon May 14 17:16:36 2007
@@ -0,0 +1,30 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+
+from twisted.trial import unittest
+
+from flumotion.component.base import scheduler
+
+
+class SchedulerTest(unittest.TestCase):
+ def testInstantiate(self):
+ scheduler.Scheduler()
More information about the flumotion-commit
mailing list