msmith - in flumotion/branches/platform-3: .
flumotion/component/producers/playlist
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Tue May 22 16:15:12 CEST 2007
Author: msmith
Date: Tue May 22 16:15:10 2007
New Revision: 4994
Added:
flumotion/branches/platform-3/flumotion/component/producers/playlist/watcher.py
Modified:
flumotion/branches/platform-3/ChangeLog
flumotion/branches/platform-3/flumotion/component/producers/playlist/Makefile.am
flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.py
flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.xml
flumotion/branches/platform-3/flumotion/component/producers/playlist/playlistparser.py
Log:
* flumotion/component/producers/playlist/Makefile.am:
* flumotion/component/producers/playlist/playlist.py:
* flumotion/component/producers/playlist/playlist.xml:
* flumotion/component/producers/playlist/playlistparser.py:
* flumotion/component/producers/playlist/watcher.py:
Merge current playlist component. Import a private copy of the
watcher code which doesn't exist in this branch.
Modified: flumotion/branches/platform-3/ChangeLog
==============================================================================
--- flumotion/branches/platform-3/ChangeLog (original)
+++ flumotion/branches/platform-3/ChangeLog Tue May 22 16:15:10 2007
@@ -1,3 +1,13 @@
+2007-05-22 Michael Smith <msmith at fluendo.com>
+
+ * flumotion/component/producers/playlist/Makefile.am:
+ * flumotion/component/producers/playlist/playlist.py:
+ * flumotion/component/producers/playlist/playlist.xml:
+ * flumotion/component/producers/playlist/playlistparser.py:
+ * flumotion/component/producers/playlist/watcher.py:
+ Merge current playlist component. Import a private copy of the
+ watcher code which doesn't exist in this branch.
+
2007-05-21 Michael Smith <msmith at fluendo.com>
* flumotion/component/producers/playlist/design:
Modified: flumotion/branches/platform-3/flumotion/component/producers/playlist/Makefile.am
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/producers/playlist/Makefile.am (original)
+++ flumotion/branches/platform-3/flumotion/component/producers/playlist/Makefile.am Tue May 22 16:15:10 2007
@@ -1,7 +1,7 @@
include $(top_srcdir)/common/python.mk
component_PYTHON = __init__.py playlist.py singledecodebin.py smartscale.py \
- playlistparser.py
+ playlistparser.py watcher.py
componentdir = $(libdir)/flumotion/python/flumotion/component/producers/playlist
component_DATA = playlist.xml
Modified: flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.py (original)
+++ flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.py Tue May 22 16:15:10 2007
@@ -33,6 +33,7 @@
import smartscale
import singledecodebin
import playlistparser
+import watcher
T_ = messages.gettexter('flumotion')
@@ -298,6 +299,8 @@
props = self.config['properties'];
self._playlistfile = props.get('playlist', None)
+ self._playlistdirectory = props.get('playlist-directory', None)
+ self._baseDirectory = props.get('base-directory', None)
self._width = props.get('width', 320)
self._height = props.get('height', 240)
@@ -316,6 +319,34 @@
self.connect_feeders(pipeline)
return pipeline
+ def _watchDirectory(self, dir):
+ self.debug("Watching directory %s", dir)
+ self._filesAdded = {}
+
+ self._directoryWatcher = watcher.DirectoryWatcher(dir)
+ self._directoryWatcher.subscribe(fileChanged=self._watchFileChanged,
+ fileDeleted=self._watchFileDeleted)
+ self._directoryWatcher.start()
+
+ def _watchFileDeleted(self, file):
+ self.debug("File deleted: %s", file)
+ if file in self._filesAdded:
+ self.playlistparser.playlist.removeItems(file)
+ self._filesAdded.pop(file)
+
+ def _watchFileChanged(self, file):
+ self.debug("File changed: %s", file)
+ if file in self._filesAdded:
+ self.debug("Removing existing items for changed playlist")
+ self.playlistparser.playlist.removeItems(file)
+
+ self._filesAdded[file] = None
+ try:
+ self.debug("Parsing file: %s", file)
+ self.playlistparser.parseFile(file, id=file)
+ except fxml.ParserError, e:
+ self.warning("Failed to parse playlist file: %r", e)
+
def connect_feeders(self, pipeline):
# Backport: add this directly here, since it isn't in
# feedcomponent010.py
@@ -334,11 +365,17 @@
playlist = playlistparser.Playlist(self)
self.playlistparser = playlistparser.PlaylistXMLParser(playlist)
- try:
- if self._playlistfile:
+ if self._baseDirectory:
+ self.playlistparser.setBaseDirectory(self._baseDirectory)
+
+ if self._playlistfile:
+ try:
self.playlistparser.parseFile(self._playlistfile)
- except fxml.ParserError, e:
- self.warning("Failed to parse playlist file: %r", e)
+ except fxml.ParserError, e:
+ self.warning("Failed to parse playlist file: %r", e)
+
+ if self._playlistdirectory:
+ self._watchDirectory(self._playlistdirectory)
return defer.succeed(None)
Modified: flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.xml
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.xml (original)
+++ flumotion/branches/platform-3/flumotion/component/producers/playlist/playlist.xml Tue May 22 16:15:10 2007
@@ -30,8 +30,13 @@
<property name="channels" type="int"
description="Audio channels to output" />
- <property name="playlist" type="string" required="yes"
- description="Location of the initial playlist file" />
+ <property name="playlist" type="string"
+ description="Location of the initial playlist file if any" />
+ <property name="playlist-directory" type="string"
+ description="Location of a directory to monitor for playlist files to add" />
+
+ <property name="base-directory" type="string"
+ description="Base directory for relative paths in playlist files" />
</properties>
</component>
</components>
@@ -49,6 +54,7 @@
<filename location="singledecodebin.py" />
<filename location="playlist.py" />
<filename location="playlistparser.py" />
+ <filename location="watcher.py" />
</directory>
</directories>
</bundle>
Modified: flumotion/branches/platform-3/flumotion/component/producers/playlist/playlistparser.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/producers/playlist/playlistparser.py (original)
+++ flumotion/branches/platform-3/flumotion/component/producers/playlist/playlistparser.py Tue May 22 16:15:10 2007
@@ -189,6 +189,13 @@
self._pending_items = []
self._discovering = False
+ self._baseDirectory = None
+
+ def setBaseDirectory(self, baseDir):
+ if not baseDir.endswith('/'):
+ baseDir = baseDir + '/'
+ self._baseDirectory = baseDir
+
def _discoverPending(self):
def _discovered(disc, is_media):
self.debug("Discovered!")
@@ -197,7 +204,11 @@
def _discoverer_done(disc, is_media):
if is_media:
self.debug("Discovery complete, media found")
- uri = "file://" + item[0]
+ filename = item[0]
+ if filename[0] != '/' and self._baseDirectory:
+ filename = self._baseDirectory + filename
+
+ uri = "file://" + filename
timestamp = item[1]
duration = item[2]
offset = item[3]
Added: flumotion/branches/platform-3/flumotion/component/producers/playlist/watcher.py
==============================================================================
--- (empty file)
+++ flumotion/branches/platform-3/flumotion/component/producers/playlist/watcher.py Tue May 22 16:15:10 2007
@@ -0,0 +1,195 @@
+# -*- 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.
+
+
+import os
+
+from twisted.internet import reactor
+
+from flumotion.common import log
+
+class BaseWatcher(log.Loggable):
+ """I watch for file changes.
+
+ I am a base class for a file watcher. I can be specialized to watch
+ any set of files.
+ """
+
+ def __init__(self, timeout):
+ """Make a file watcher object.
+
+ @param timeout: timeout between checks, in seconds
+ @type timeout: int
+ """
+ self.timeout = timeout
+ self._reset()
+ self._subscribeId = 0
+ self.subscribers = {}
+
+ def _reset(self):
+ self._stableData = {}
+ self._changingData = {}
+ self._delayedCall = None
+
+ def _subscribe(self, **events):
+ """Subscribe to events.
+
+ @param kwargs: The events to subscribe to. Subclasses are
+ expected to formalize this dict, specifying which events they
+ support via declaring their kwargs explicitly.
+
+ @returns: A subscription ID that can later be passed to
+ unsubscribe().
+ """
+ sid = self._subscribeId
+ self._subscribeId += 1
+ self.subscribers[sid] = events
+ return sid
+
+ def subscribe(self, fileChanged=None, fileDeleted=None):
+ """Subscribe to events.
+
+ @param fileChanged: A function to call when a file changes. This
+ function will only be called if the file's details (size, mtime)
+ do not change during the timeout period.
+ @type fileChanged: filename -> None
+ @param fileDeleted: A function to call when a file is deleted.
+ @type fileDeleted: filename -> None
+
+ @returns: A subscription ID that can later be passed to
+ unsubscribe().
+ """
+ return self._subscribe(fileChanged=fileChanged,
+ fileDeleted=fileDeleted)
+
+ def unsubscribe(self, id):
+ """Unsubscribe from file change notifications.
+
+ @param id: Subscription ID received from subscribe()
+ """
+ del self.subscribers[id]
+
+ def event(self, event, *args, **kwargs):
+ """Fire an event.
+
+ This method is intended for use by object implementations.
+ """
+ for s in self.subscribers.values():
+ if s[event]:
+ s[event](*args, **kwargs)
+
+ def start(self):
+ """Start checking for file changes.
+
+ Subscribers will be notified asynchronously of changes to the
+ watched files.
+ """
+ def checkFiles():
+ self.log("checking for file changes")
+ new = self.getFileData()
+ changing = self._changingData
+ stable = self._stableData
+ for f in new:
+ if f not in changing:
+ if f in stable and new[f] == stable[f]:
+ # no change
+ pass
+ else:
+ self.debug('change start noted for %s', f)
+ changing[f] = new[f]
+ else:
+ if new[f] == changing[f]:
+ self.debug('change finished for %s', f)
+ del changing[f]
+ stable[f] = new[f]
+ self.event('fileChanged', f)
+ else:
+ self.log('change continues for %s', f)
+ changing[f] = new[f]
+ for f in stable:
+ if f not in new:
+ # deletion
+ del stable[f]
+ if f in changing:
+ del changing[f]
+ self.debug('file %s has been deleted', f)
+ self.event('fileDeleted', f)
+ self._delayedCall = reactor.callLater(self.timeout,
+ checkFiles)
+
+ assert self._delayedCall is None
+ checkFiles()
+
+ def stop(self):
+ """Stop checking for file changes.
+ """
+ self._delayedCall.cancel()
+ self._reset()
+
+ def getFileData(self):
+ """
+ @returns: a dict, {filename => DATA}
+ DATA can be anything. In the default implementation it is a pair
+ of (mtime, size).
+ """
+ ret = {}
+ for f in self.getFilesToStat():
+ try:
+ stat = os.stat(f)
+ ret[f] = (stat.st_mtime, stat.st_size)
+ except OSError, e:
+ self.debug('could not read file %f: %s', f,
+ log.getExceptionMessage(e))
+ return ret
+
+ def getFilesToStat(self):
+ """
+ @returns: sequence of filename
+ """
+ raise NotImplementedError
+
+class DirectoryWatcher(BaseWatcher):
+ """
+ Directory Watcher
+ Watches a directory for new files.
+ """
+
+ def __init__(self, path, ignorefiles=(), timeout=30):
+ BaseWatcher.__init__(self, timeout)
+ self.path = path
+ self._ignorefiles = ignorefiles
+
+ def getFilesToStat(self):
+ return [os.path.join(self.path, f)
+ for f in os.listdir(self.path)
+ if f not in self._ignorefiles]
+
+class FilesWatcher(BaseWatcher):
+ """
+ Watches a collection of files for modifications.
+ """
+
+ def __init__(self, files, timeout=30):
+ BaseWatcher.__init__(self, timeout)
+ self._files = files
+
+ def getFilesToStat(self):
+ return self._files
More information about the flumotion-commit
mailing list