[pitivi: 10/14] Refactor pitivi.sourcelist. Tweak missing-plugin handling.



commit 2ebbd62fd98a67425f35f2751d1b86e5ce5ce270
Author: Alessandro Decina <alessandro d gmail com>
Date:   Fri Jun 19 20:19:14 2009 +0200

    Refactor pitivi.sourcelist. Tweak missing-plugin handling.
    
    Refactor pitivi.sourcelist so that it removes the (broken) mapping methods and
    emits saner signals (source-added, source-removed rather than file_added and
    file_removed).
    Also tweak missing-plugin handling in discoverer so that we can rediscover a
    source more quickly after installing missing plugins.

 pitivi/application.py         |   15 ++-
 pitivi/discoverer.py          |  157 ++++++++++++++++++++----------
 pitivi/formatters/etree.py    |    2 +-
 pitivi/project.py             |    9 +--
 pitivi/sourcelist.py          |  214 +++++++++++++++++++----------------------
 pitivi/ui/mainwindow.py       |   30 +++---
 pitivi/ui/sourcelist.py       |   12 +-
 pitivi/ui/timeline.py         |    4 +-
 tests/test_discoverer.py      |   31 ++++++-
 tests/test_etree_formatter.py |    4 +-
 tests/test_sourcelist.py      |  104 ++++++++++++++++++++
 11 files changed, 370 insertions(+), 212 deletions(-)
---
diff --git a/pitivi/application.py b/pitivi/application.py
index 57b01c6..1a9a512 100644
--- a/pitivi/application.py
+++ b/pitivi/application.py
@@ -50,6 +50,7 @@ from pitivi.ui.mainwindow import PitiviMainWindow
 from pitivi.projectmanager import ProjectManager
 from pitivi.undo import UndoableActionLog, DebugActionLogObserver
 from pitivi.timeline.timeline_undo import TimelineLogObserver
+from pitivi.sourcelist_undo import SourceListLogObserver
 
 # FIXME : Speedup loading time
 # Currently we load everything in one go
@@ -140,6 +141,7 @@ class Pitivi(Loggable, Signallable):
         self.debug_action_log_observer = DebugActionLogObserver()
         self.debug_action_log_observer.startObserving(self.action_log)
         self.timelineLogObserver = TimelineLogObserver(self.action_log)
+        self.sourcelist_log_observer = SourceListLogObserver(self.action_log)
 
     #{ Shutdown methods
 
@@ -190,7 +192,9 @@ class Pitivi(Loggable, Signallable):
 
     def _projectManagerNewProjectLoaded(self, projectManager, project):
         self.current = project
+        self.action_log.clean()
         self.timelineLogObserver.startObserving(project.timeline)
+        self.sourcelist_log_observer.startObserving(project.sources)
         self.emit("new-project-loaded", project)
 
     def _projectManagerNewProjectFailed(self, projectManager, uri, exception):
@@ -200,7 +204,6 @@ class Pitivi(Loggable, Signallable):
         return self.emit("closing-project", project)
 
     def _projectManagerProjectClosed(self, projectManager, project):
-        self.action_log.clean()
         self.timelineLogObserver.stopObserving(project.timeline)
         self.current = None
         self.emit("project-closed", project)
@@ -254,11 +257,11 @@ class InteractivePitivi(Pitivi):
             self.projectManager.newBlankProject()
             uris = ["file://" + os.path.abspath(path) for path in args]
             if options.add_to_timeline:
-                self.current.sources.connect("file_added",
-                        self._addSourceCb, uris)
+                self.current.sources.connect("source-added",
+                        self._sourceAddedCb, uris)
                 self.current.sources.connect("discovery-error",
                         self._discoveryErrorCb, uris)
-            self.current.sources.addUris(uris)
+                self.current.sources.addUris(uris)
 
         # run the mainloop
         self.mainloop.run()
@@ -294,7 +297,7 @@ class InteractivePitivi(Pitivi):
 
         return True
 
-    def _addSourceCb(self, unused_sourcelist, factory, startup_uris):
+    def _sourceAddedCb(self, unused_sourcelist, factory, startup_uris):
         if self._maybePopStartupUri(startup_uris, factory.uri):
             self.current.timeline.addSourceFactory(factory)
 
@@ -311,7 +314,7 @@ class InteractivePitivi(Pitivi):
             return False
 
         if not startup_uris:
-            self.current.sources.disconnect_by_function(self._addSourceCb)
+            self.current.sources.disconnect_by_function(self._sourceAddedCb)
             self.current.sources.disconnect_by_function(self._discoveryErrorCb)
 
         return True
diff --git a/pitivi/discoverer.py b/pitivi/discoverer.py
index e02be4b..3b07b80 100644
--- a/pitivi/discoverer.py
+++ b/pitivi/discoverer.py
@@ -31,6 +31,9 @@ import gobject
 gobject.threads_init()
 import gst
 import gst.pbutils
+from gst.pbutils import INSTALL_PLUGINS_SUCCESS, \
+        INSTALL_PLUGINS_PARTIAL_SUCCESS, INSTALL_PLUGINS_USER_ABORT, \
+        INSTALL_PLUGINS_STARTED_OK
 import tempfile
 from base64 import urlsafe_b64encode
 
@@ -90,6 +93,8 @@ class Discoverer(Signallable, Loggable):
         self.missing_plugin_messages = []
         self.dynamic_elements = []
         self.thumbnails = {}
+        self.missing_plugin_details = []
+        self.missing_plugin_descriptions = []
 
     def _resetPipeline(self):
         # finish current, cleanup
@@ -152,33 +157,105 @@ class Discoverer(Signallable, Loggable):
         if not self.missing_plugin_messages:
             return False
 
-        missing_plugin_details = []
-        missing_plugin_descriptions = []
         for message in self.missing_plugin_messages:
             detail = \
                     gst.pbutils.missing_plugin_message_get_installer_detail(message)
             description = \
                     gst.pbutils.missing_plugin_message_get_description(message)
 
-            missing_plugin_details.append(detail)
-            missing_plugin_descriptions.append(description)
+            self.missing_plugin_details.append(detail)
+            self.missing_plugin_descriptions.append(description)
 
-        result = self.emit('missing-plugins', self.current_uri,
-                missing_plugin_details, missing_plugin_descriptions)
+        return True
 
-        if result == gst.pbutils.INSTALL_PLUGINS_STARTED_OK:
-            # don't emit an error yet
-            self.error = None
-            self.error_detail = None
-            res = True
+    def _installMissingPluginsCallback(self, result, factory):
+        rescan = False
+
+        if result in (INSTALL_PLUGINS_SUCCESS,
+                INSTALL_PLUGINS_PARTIAL_SUCCESS):
+            gst.update_registry()
+            rescan = True
+        elif result == INSTALL_PLUGINS_USER_ABORT \
+                and factory.getOutputStreams():
+            self._emitDone(factory)
         else:
-            if self.error is None:
-                self.error = _('Missing plugins:\n%s') % \
-                             '\n'.join(missing_plugin_descriptions)
-                self.error_detail = ''
-            res = False
+            self._emitErrorMissingPlugins()
+
+        self._finishAnalysisAfterResult(rescan=rescan)
+
+    def _emitError(self):
+        self.emit("discovery-error", self.current_uri, self.error, self.error_detail)
+
+    def _emitErrorMissingPlugins(self):
+        self.error = _("Missing plugins:\n%s") % \
+                "\n".join(self.missing_plugin_descriptions)
+        self.error_detail = ""
+        self._emitError()
+
+    def _emitDone(self, factory):
+        self.emit("discovery-done", self.current_uri, factory)
+
+    def _emitResult(self):
+        # we got a gst error, error out ASAP
+        if self.error:
+            self._emitError()
+            return True
+
+        have_video, have_audio, have_image = self._getCurrentStreamTypes()
+        missing_plugins = bool(self.missing_plugin_details)
+
+        if not self.current_streams and not missing_plugins:
+            # woot, nothing decodable
+            self.error = _('Can not decode file.')
+            self.error_detail = _("The given file does not contain audio, "
+                    "video or picture streams.")
+            self._emitError()
+            return True
+
+        # construct the factory with the streams we found
+        if have_image:
+            factory = PictureFileSourceFactory(self.current_uri)
+        else:
+            factory = FileSourceFactory(self.current_uri)
+
+        factory.duration = self.current_duration
+        for stream in self.current_streams:
+            factory.addOutputStream(stream)
+
+        if not missing_plugins:
+            # make sure that we could query the duration (if it's an image, we
+            # assume it's got infinite duration)
+            is_image = have_image and len(self.current_streams) == 1
+            if self.current_duration == gst.CLOCK_TIME_NONE and not is_image:
+                self.error =_("Could not establish the duration of the file.")
+                self.error_detail = _("This clip seems to be in a format "
+                        "which cannot be accessed in a random fashion.")
+                self._emitError()
+                return True
+
+            self._emitDone(factory)
+            return True
+
+        def callback(result):
+            self._installMissingPluginsCallback(result, factory)
+
+        res = self.emit("missing-plugins", self.current_uri, factory,
+                self.missing_plugin_details,
+                self.missing_plugin_descriptions,
+                callback)
+        if res is None or res != INSTALL_PLUGINS_STARTED_OK:
+            # no missing-plugins handlers
+            if factory.getOutputStreams():
+                self._emitDone(factory)
+            else:
+                self._emitErrorMissingPlugins()
 
-        return res
+            return True
+
+
+        # plugins are being installed, processing will continue when
+        # self._installMissingPluginsCallback is called by the application
+        return False
 
     def _finishAnalysis(self):
         """
@@ -188,49 +265,21 @@ class Discoverer(Signallable, Loggable):
         if self.timeout_id:
             self._removeTimeout()
 
-        missing_plugins = self._checkMissingPlugins()
+        # check if there are missing plugins before calling _resetPipeline as we
+        # are going to pop messagess off the bus
+        self._checkMissingPlugins()
         self._resetPipeline()
-        if not self.current_streams and self.error is None:
-            # EOS and no decodable streams?
-            self.error = _('No streams found')
-            self.error_detail = ""
-
-        if len(self.current_streams) == 1:
-            stream = self.current_streams[0]
-            is_image = isinstance(stream, VideoStream) and stream.is_image
-        else:
-            is_image = False
-
-        if self.error:
-            self.emit('discovery-error', self.current_uri, self.error, self.error_detail)
-        elif self.current_duration == gst.CLOCK_TIME_NONE and not is_image:
-            self.emit('discovery-error', self.current_uri,
-                      _("Could not establish the duration of the file."),
-                      _("This clip seems to be in a format which cannot be accessed in a random fashion."))
-        else:
-            have_video, have_audio, have_image = self._getCurrentStreamTypes()
-            if have_video or have_audio:
-                factory = FileSourceFactory(self.current_uri)
-            elif have_image:
-                factory = PictureFileSourceFactory(self.current_uri)
-            else:
-                # woot, nothing decodable
-                self.error = _('Can not decode file.')
-                self.error_detail = _('The given file does not contain audio, video or picture streams.')
-                factory = None
-
-            if factory is not None:
-                factory.duration = self.current_duration
-
-                for stream in self.current_streams:
-                    factory.addOutputStream(stream)
 
-            self.emit('discovery-done', self.current_uri, factory)
+        # emit discovery-done, discovery-error or missing-plugins
+        if self._emitResult():
+            self._finishAnalysisAfterResult()
 
+    def _finishAnalysisAfterResult(self, rescan=False):
         self.info("Cleaning up after finished analyzing %s", self.current_uri)
         self._resetState()
 
-        self.queue.pop(0)
+        if not rescan:
+            self.queue.pop(0)
         # restart an analysis if there's more...
         if self.queue:
             self._scheduleAnalysis()
diff --git a/pitivi/formatters/etree.py b/pitivi/formatters/etree.py
index ed238a2..b82ad46 100644
--- a/pitivi/formatters/etree.py
+++ b/pitivi/formatters/etree.py
@@ -639,7 +639,7 @@ class ElementTreeFormatter(Formatter):
             return
 
         self._replaceMatchingOldFactory(factory, old_factories)
-        project.sources.addFactory(factory=factory)
+        project.sources.addFactory(factory)
 
         closure["rediscovered"] += 1
         if closure["rediscovered"] == len(old_factories):
diff --git a/pitivi/project.py b/pitivi/project.py
index 52f93ae..1c90144 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -59,13 +59,11 @@ class Project(Signallable, Loggable):
     @type loaded: C{bool}
 
     Signals:
-     - C{missing-plugins} : A plugin is missing for the given uri
      - C{loaded} : The project is now fully loaded.
     """
 
     __signals__ = {
         "settings-changed" : None,
-        "missing-plugins": ["uri", "detail", "description"],
         }
 
     def __init__(self, name="", uri=None, **kwargs):
@@ -81,13 +79,11 @@ class Project(Signallable, Loggable):
         self.uri = uri
         self.urichanged = False
         self.format = None
-        self.sources = SourceList(self)
+        self.sources = SourceList()
 
         self.settingssigid = 0
         self._dirty = False
 
-        self.sources.connect('missing-plugins', self._sourceListMissingPluginsCb)
-
         self.timeline = Timeline()
 
         self.factory = TimelineSourceFactory(self.timeline)
@@ -161,9 +157,6 @@ class Project(Signallable, Loggable):
 
     #}
 
-    def _sourceListMissingPluginsCb(self, source_list, uri, detail, description):
-        return self.emit('missing-plugins', uri, detail, description)
-
     #{ Save and Load features
 
     def save(self, location=None, overwrite=False):
diff --git a/pitivi/sourcelist.py b/pitivi/sourcelist.py
index 37702b3..bc64d99 100644
--- a/pitivi/sourcelist.py
+++ b/pitivi/sourcelist.py
@@ -3,6 +3,7 @@
 #       pitivi/sourcelist.py
 #
 # Copyright (c) 2005, Edward Hervey <bilboed bilboed com>
+# Copyright (c) 2009, Alessandro Decina <alessandro d gmail com>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -22,167 +23,143 @@
 """
 Handles the list of source for a project
 """
+
 import urllib
 from pitivi.discoverer import Discoverer
 from pitivi.signalinterface import Signallable
 from pitivi.log.loggable import Loggable
 
+class SourceListError(Exception):
+    pass
+
 class SourceList(Signallable, Loggable):
+    discovererClass = Discoverer
+
     """
-    Contains the sources for a project, stored as FileSourceFactory
+    Contains the sources for a project, stored as SourceFactory objects.
 
-    @ivar project: The owner project
-    @type project: L{Project}
-    @ivar discoverer: The discoverer used
+    @ivar discoverer: The discoverer object used internally
     @type discoverer: L{Discoverer}
-    @ivar sources: The sources
-    @type sources: Dictionnary of uri => factory
 
     Signals:
-     - C{file_added} : A file has been completely discovered and is valid.
-     - C{file_removed} : A file was removed from the SourceList.
+     - C{source-added} : A source has been discovered and added to the SourceList.
+     - C{source-removed} : A source was removed from the SourceList.
+     - C{missing-plugins} : A source has been discovered but some plugins are
+       missing in order to decode all of its streams.
      - C{discovery-error} : The given uri is not a media file.
-     - C{tmp_is_ready} : The temporary uri given to the SourceList is ready to use.
      - C{ready} : No more files are being discovered/added.
      - C{starting} : Some files are being discovered/added.
     """
 
     __signals__ = {
-        "file_added" : ["factory"],
-        "file_removed" : ["uri"],
+        "ready" : [],
+        "starting" : [],
+        "missing-plugins": ["uri", "factory", "details", "descriptions"],
+        "source-added" : ["factory"],
+        "source-removed" : ["uri"],
         "discovery-error" : ["uri", "reason"],
-        "tmp_is_ready": ["factory"],
-        "ready" : None,
-        "starting" : None,
-        "missing-plugins": ["uri", "detail", "description"]
         }
 
-    def __init__(self, project=None):
+    def __init__(self):
         Loggable.__init__(self)
-        self.log("new sourcelist for project %s", project)
-        self.project = project
-        self.sources = {}
-        self._sourceindex = []
-        self.tempsources = {}
-        self.discoverer = Discoverer()
+        Signallable.__init__(self)
+        self._sources = {}
+        self._ordered_sources = []
+
+        self.discoverer = self.discovererClass()
         self.discoverer.connect("discovery-error", self._discoveryErrorCb)
         self.discoverer.connect("discovery-done", self._discoveryDoneCb)
         self.discoverer.connect("starting", self._discovererStartingCb)
         self.discoverer.connect("ready", self._discovererReadyCb)
         self.discoverer.connect("missing-plugins",
                 self._discovererMissingPluginsCb)
-        self.missing_plugins = {}
 
-    def __contains__(self, uri):
-        return self.sources.__contains__(uri)
 
-    def __delitem__(self, uri):
-        try:
-            self.sources.__delitem__(uri)
-            self._sourceindex.remove(uri)
-        except KeyError:
-            pass
-        else:
-            # emit deleted item signal
-            self.emit("file_removed", uri)
+    def addUri(self, uri):
+        """
+        Add c{uri} to the source list.
 
-    def __getitem__(self, uri):
-        try:
-            res = self.sources.__getitem__(uri)
-        except KeyError:
-            res = None
-        return res
+        The uri will be analyzed before being added.
+        """
+        if uri in self._sources:
+            raise SourceListError("URI already present in the source list", uri)
 
-    def __iter__(self):
-        """ returns an (uri, factory) iterator over the sources """
-        return self.sources.iteritems()
+        uri = urllib.unquote(uri)
+        self._sources[uri] = None
 
-    def addUri(self, uri):
-        """ Add the uri to the list of sources, will be discovered """
-        # here we add the uri and emit a signal
-        # later on the existence of the file will be confirmed or not
-        # Until it's confirmed, the uri stays in the temporary list
-        # for the moment, we pass it on to the Discoverer
-        if uri in self.sources.keys():
-            return
-        self.sources[uri] = None
         self.discoverer.addUri(uri)
 
     def addUris(self, uris):
-        """ Add the list of uris to the list of sources, they will be discovered """
-        # same as above but for a list
-        rlist = []
+        """
+        Add c{uris} to the source list.
+
+        The uris will be analyzed before being added.
+        """
         for uri in uris:
-            uri = urllib.unquote(uri)
-            if not uri in self.sources.keys():
-                self.sources[uri] = None
-                rlist.append(uri)
+            self.addUri(uri)
 
-        self.discoverer.addUris(rlist)
+    def removeUri(self, uri):
+        """
+        Remove the factory for c{uri} from the source list.
+        """
+        try:
+            factory = self._sources.pop(uri)
+        except KeyError:
+            raise SourceListError("URI not in the sourcelist", uri)
 
-    def addTmpUri(self, uri):
-        """ Adds a temporary uri, will not be saved """
-        uri = urllib.unquote(uri)
-        if uri in self.sources.keys():
-            return
-        self.tempsources[uri] = None
-        self.discoverer.addUri(uri)
+        try:
+            self._ordered_sources.remove(factory)
+        except ValueError:
+            # this can only happen if discoverer hasn't finished scanning the
+            # source, so factory must be None
+            assert factory is None
+
+        self.emit("source-removed", uri, factory)
+
+    def getUri(self, uri):
+        """
+        Get the source corresponding to C{uri}.
+        """
+        factory = self._sources.get(uri, None)
+        if factory is None:
+            raise SourceListError("URI not in the sourcelist", uri)
+
+        return factory
 
-    def removeFactory(self, factory):
-        """ Remove a file using it's objectfactory """
-        # TODO
-        # remove an item using the factory as a key
-        # otherwise just use the __delitem__
-        # del self[uri]
-        rmuri = []
-        for uri, fact in self.sources.iteritems():
-            if fact == factory:
-                rmuri.append(uri)
-        for uri in rmuri:
-            del self[uri]
-
-    # FIXME : Invert the order of the arguments so we can just have:
-    # addFactory(self, factory, uri=None)
-    def addFactory(self, uri=None, factory=None):
+    def addFactory(self, factory):
         """
         Add an objectfactory for the given uri.
         """
-        if uri==None:
-            uri = factory.uri
-        if uri in self and self[uri]:
-            raise Exception("We already have an objectfactory for uri %s", uri)
-        self.sources[uri] = factory
-        self._sourceindex.append(uri)
-        self.emit("file_added", factory)
+        if self._sources.get(factory.uri, None) is not None:
+            raise SourceListError("We already have a factory for this uri",
+                    factory.uri)
+
+        self._sources[factory.uri] = factory
+        self._ordered_sources.append(factory)
+        self.emit("source-added", factory)
 
     def getSources(self):
         """ Returns the list of sources used.
 
         The list will be ordered by the order in which they were added
         """
-        res = []
-        for i in self._sourceindex:
-            res.append(self[i])
-        return res
-
-    def _discoveryDoneCb(self, unused_discoverer, uri, factory):
-        # callback from finishing analyzing factory
-        if uri in self.tempsources:
-            self.tempsources[uri] = factory
-            self.emit("tmp_is_ready", factory)
-        elif uri in self.sources:
-            self.addFactory(uri, factory)
-
-    def _discoveryErrorCb(self, unused_discoverer, uri, reason, extra):
-        if self.missing_plugins.pop(uri, None) is None:
-            # callback from the discoverer's 'discovery-error' signal
-            # remove it from the list
-            self.emit("discovery-error", uri, reason, extra)
-
-        if uri in self.sources and not self.sources[uri]:
-            del self.sources[uri]
-        elif uri in self.tempsources:
-            del self.tempsources[uri]
+        return self._ordered_sources
+
+    def _discoveryDoneCb(self, discoverer, uri, factory):
+        if factory.uri not in self._sources:
+            # the source was removed while it was being scanned
+            return
+
+        self.addFactory(factory)
+
+    def _discoveryErrorCb(self, discoverer, uri, reason, extra):
+        try:
+            del self._sources[uri]
+        except KeyError:
+            # the source was removed while it was being scanned
+            pass
+
+        self.emit("discovery-error", uri, reason, extra)
 
     def _discovererStartingCb(self, unused_discoverer):
         self.emit("starting")
@@ -190,6 +167,11 @@ class SourceList(Signallable, Loggable):
     def _discovererReadyCb(self, unused_discoverer):
         self.emit("ready")
 
-    def _discovererMissingPluginsCb(self, discoverer, uri, detail, description):
-        self.missing_plugins[uri] = True
-        return self.emit('missing-plugins', uri, detail, description)
+    def _discovererMissingPluginsCb(self, discoverer, uri, factory,
+            details, descriptions, missingPluginsCallback):
+        if factory.uri not in self._sources:
+            # the source was removed while it was being scanned
+            return None
+
+        return self.emit('missing-plugins', uri, factory,
+                details, descriptions, missingPluginsCallback)
diff --git a/pitivi/ui/mainwindow.py b/pitivi/ui/mainwindow.py
index 09f3454..5e42282 100644
--- a/pitivi/ui/mainwindow.py
+++ b/pitivi/ui/mainwindow.py
@@ -159,7 +159,6 @@ class PitiviMainWindow(gtk.Window, Loggable):
         self.error_dialogbox = None
         self.settings = instance.settings
         self.is_fullscreen = self.settings.mainWindowFullScreen
-        self.missing_plugins = []
         self.timelinepos = 0
         self.prefsdialog = None
         create_stock_icons()
@@ -453,23 +452,14 @@ class PitiviMainWindow(gtk.Window, Loggable):
 
 ## Missing Plugin Support
 
-    def _installPlugins(self, details):
+    def _installPlugins(self, details, missingPluginsCallback):
         context = gst.pbutils.InstallPluginsContext()
         context.set_xid(self.window.xid)
 
         res = gst.pbutils.install_plugins_async(details, context,
-                self._installPluginsAsyncCb)
+                missingPluginsCallback)
         return res
 
-    def _installPluginsAsyncCb(self, result):
-        missing_plugins, self.missing_plugins = self.missing_plugins, []
-
-        if result != gst.pbutils.INSTALL_PLUGINS_SUCCESS:
-            return
-
-        gst.update_registry()
-        self.app.current.sources.addUris(missing_plugins)
-
 ## UI Callbacks
 
     def _configureCb(self, unused_widget, event):
@@ -669,6 +659,7 @@ class PitiviMainWindow(gtk.Window, Loggable):
     def _projectManagerNewProjectLoadedCb(self, projectManager, project):
         self.log("A NEW project is loaded, update the UI!")
         self.project = project
+        self._connectToProjectSources(project.sources)
         if project.timeline.duration > 0:
             self.render_button.set_sensitive(True)
 
@@ -749,6 +740,7 @@ class PitiviMainWindow(gtk.Window, Loggable):
 
     def _projectManagerProjectClosedCb(self, projectManager, project):
         # we must disconnect from the project pipeline before it is released
+        self._disconnectFromProjectSources(project.sources)
         self.viewer.setAction(None)
         self.viewer.setPipeline(None)
         return False
@@ -802,6 +794,12 @@ class PitiviMainWindow(gtk.Window, Loggable):
 
         dialog.destroy()
 
+    def _connectToProjectSources(self, sourcelist):
+        sourcelist.connect("missing-plugins", self._sourceListMissingPluginsCb)
+
+    def _disconnectFromProjectSources(self, sourcelist):
+        sourcelist.disconnect_by_func(self._sourceListMissingPluginsCb)
+
     def _actionLogCommit(self, action_log, stack, nested):
         if nested:
             return
@@ -858,10 +856,10 @@ class PitiviMainWindow(gtk.Window, Loggable):
             sett = self.project.getSettings()
             self.viewer.setDisplayAspectRatio(float(sett.videopar * sett.videowidth) / float(sett.videoheight))
 
-    @handler(project, "missing-plugins")
-    def _projectMissingPluginsCb(self, project, uri, detail, message):
-        self.missing_plugins.append(uri)
-        return self._installPlugins(detail)
+    def _sourceListMissingPluginsCb(self, project, uri, factory,
+            details, descriptions, missingPluginsCallback):
+        res = self._installPlugins(details, missingPluginsCallback)
+        return res
 
 ## Current Project Pipeline
 
diff --git a/pitivi/ui/sourcelist.py b/pitivi/ui/sourcelist.py
index 8621db5..d4c1d47 100644
--- a/pitivi/ui/sourcelist.py
+++ b/pitivi/ui/sourcelist.py
@@ -348,13 +348,13 @@ class SourceList(gtk.VBox, Loggable):
 
         """
         self.project_signals.connect(
-            project.sources, "file_added", None, self._fileAddedCb)
+            project.sources, "source-added", None, self._sourceAddedCb)
         self.project_signals.connect(
-            project.sources, "file_removed", None, self._fileRemovedCb)
+            project.sources, "source-removed", None, self._sourceRemovedCb)
         self.project_signals.connect(
             project.sources, "discovery-error", None, self._discoveryErrorCb)
-        self.project_signals.connect(
-            project.sources, "missing-plugins", None, self._missingPluginsCb)
+        #self.project_signals.connect(
+        #    project.sources, "missing-plugins", None, self._missingPluginsCb)
         self.project_signals.connect(
             project.sources, "ready", None, self._sourcesStoppedImportingCb)
         self.project_signals.connect(
@@ -472,11 +472,11 @@ class SourceList(gtk.VBox, Loggable):
 
     # sourcelist callbacks
 
-    def _fileAddedCb(self, unused_sourcelist, factory):
+    def _sourceAddedCb(self, unused_sourcelist, factory):
         """ a file was added to the sourcelist """
         self._addFactory(factory)
 
-    def _fileRemovedCb(self, unused_sourcelist, uri):
+    def _sourceRemovedCb(self, sourcelist, uri, factory):
         """ the given uri was removed from the sourcelist """
         # find the good line in the storemodel and remove it
         model = self.storemodel
diff --git a/pitivi/ui/timeline.py b/pitivi/ui/timeline.py
index a3e1c2e..0eb7ee1 100644
--- a/pitivi/ui/timeline.py
+++ b/pitivi/ui/timeline.py
@@ -277,7 +277,7 @@ class Timeline(gtk.Table, Loggable, Zoomable):
         self.warning("self._factories:%r, self._temp_objects:%r",
                      not not self._factories,
                      not not self._temp_objects)
-        if not self._factories:
+        if self._factories is None:
             atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])
             self.drag_get_data(context, atom, timestamp)
             self.drag_highlight()
@@ -324,7 +324,7 @@ class Timeline(gtk.Table, Loggable, Zoomable):
             uris = selection.data.split("\n")
         else:
             context.finish(False, False, timestamp)
-        self._factories = [self.project.sources[uri] for uri in uris]
+        self._factories = [self.project.sources.getUri(uri) for uri in uris]
         context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
         return True
 
diff --git a/tests/test_discoverer.py b/tests/test_discoverer.py
index 4451c47..36ef727 100644
--- a/tests/test_discoverer.py
+++ b/tests/test_discoverer.py
@@ -365,7 +365,6 @@ class TestStateChange(TestCase):
         self.error_detail = debug
 
     def discoveryDoneCb(self, disc, uri, factory):
-        self.failUnlessEqual(factory.duration, 10 * gst.SECOND)
         self.factories.append(factory)
 
     def testBusStateChangedIgnored(self):
@@ -457,3 +456,33 @@ class TestStateChange(TestCase):
         self.failUnless(isinstance(factory, PictureFileSourceFactory))
         self.failUnlessEqual(len(factory.output_streams), 1)
 
+    def testDurationCheckImage(self):
+        self.discoverer.current_duration = gst.CLOCK_TIME_NONE
+        pngdec = gst.element_factory_make('pngdec')
+        self.discoverer.pipeline.add(pngdec)
+        pad = pngdec.get_pad('src')
+        caps = gst.Caps(pad.get_caps()[0])
+        caps[0]['width'] = 320
+        caps[0]['height'] = 240
+        caps[0]['framerate'] = gst.Fraction(0, 1)
+        pad.set_caps(caps)
+        self.discoverer._newDecodedPadCb(None, pad, False)
+        self.discoverer.addUri('illbepopped')
+        self.discoverer._finishAnalysis()
+
+        self.failUnlessEqual(self.error, None)
+        self.failUnlessEqual(self.discoverer.current_duration,
+                gst.CLOCK_TIME_NONE)
+
+    def testDurationCheckNonImage(self):
+        self.discoverer.current_duration = gst.CLOCK_TIME_NONE
+        pad = gst.Pad('src', gst.PAD_SRC)
+        pad.set_caps(gst.Caps('audio/x-raw-int'))
+        self.discoverer._newDecodedPadCb(None, pad, False)
+        self.discoverer.addUri('illbepopped')
+        self.discoverer._finishAnalysis()
+
+        self.failUnlessEqual(self.error,
+                "Could not establish the duration of the file.")
+        self.failUnlessEqual(self.discoverer.current_duration,
+                gst.CLOCK_TIME_NONE)
diff --git a/tests/test_etree_formatter.py b/tests/test_etree_formatter.py
index 166dad0..611d041 100644
--- a/tests/test_etree_formatter.py
+++ b/tests/test_etree_formatter.py
@@ -294,7 +294,7 @@ class TestFormatterSave(TestCase):
 
         project = Project()
         project.timeline = timeline
-        project.sources.addFactory("meh", source1)
+        project.sources.addFactory(source1)
 
         element = self.formatter._serializeProject(project)
 
@@ -516,7 +516,7 @@ class TestFormatterLoad(TestCase):
 
         project = Project()
         project.timeline = timeline
-        project.sources.addFactory("meh", source1)
+        project.sources.addFactory(source1)
 
         element = self.formatter._serializeProject(project)
 
diff --git a/tests/test_sourcelist.py b/tests/test_sourcelist.py
new file mode 100644
index 0000000..b128047
--- /dev/null
+++ b/tests/test_sourcelist.py
@@ -0,0 +1,104 @@
+# PiTiVi , Non-linear video editor
+#
+#       pitivi/sourcelist.py
+#
+# Copyright (c) 2009, Alessandro Decina <alessandro d gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+from unittest import TestCase
+from pitivi.sourcelist import SourceList, SourceListError
+from pitivi.discoverer import Discoverer
+from pitivi.factories.file import FileSourceFactory
+
+class FakeDiscoverer(Discoverer):
+    def _scheduleAnalysis(self):
+        pass
+
+
+class FakeSourceList(SourceList):
+    discovererClass = FakeDiscoverer
+
+
+class TestSourceList(TestCase):
+    def setUp(self):
+        self.sourcelist = SourceList()
+
+    def testAddUriDiscoveryOk(self):
+        """
+        Test the simple case of adding an uri.
+        """
+        uri = "file:///ciao"
+        factory = FileSourceFactory(uri)
+        self.sourcelist.addUri(uri)
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 0)
+        self.failUnlessRaises(SourceListError, self.sourcelist.addUri, uri)
+
+        # mock discovery-done
+        self.sourcelist.discoverer.emit("discovery-done", uri, factory)
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 1)
+
+        # can't add again
+        self.failUnlessRaises(SourceListError, self.sourcelist.addUri, uri)
+
+    def testAddUriDiscoveryOkSourceGone(self):
+        """
+        Test that we don't explode if discoverer finishes analyzing a source
+        that in the meantime was removed.
+        """
+        uri = "file:///ciao"
+        factory = FileSourceFactory(uri)
+        self.sourcelist.addUri(uri)
+        self.sourcelist.removeUri(uri)
+
+        self.sourcelist.discoverer.emit("discovery-done", uri, factory)
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 0)
+
+        # this shouldn't fail since we removed the factory before the discovery
+        # was complete
+        self.sourcelist.addUri(uri)
+
+    def testAddUriDiscoveryErrorSourceGone(self):
+        """
+        Same as the test above, but testing the discovery-error handler.
+        """
+        uri = "file:///ciao"
+        factory = FileSourceFactory(uri)
+        self.sourcelist.addUri(uri)
+        self.sourcelist.removeUri(uri)
+
+        self.sourcelist.discoverer.emit("discovery-error", uri,
+                "error", "verbose debug")
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 0)
+
+        # this shouldn't fail since we removed the factory before the discovery
+        # was complete
+        self.sourcelist.addUri(uri)
+
+    def testAddUriDiscoveryError(self):
+        uri = "file:///ciao"
+        factory = FileSourceFactory(uri)
+        self.sourcelist.addUri(uri)
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 0)
+        self.failUnlessRaises(SourceListError, self.sourcelist.addUri, uri)
+
+        # mock discovery-done
+        self.sourcelist.discoverer.emit("discovery-error", uri,
+                "error", "verbose debug")
+        self.failUnlessEqual(len(self.sourcelist.getSources()), 0)
+
+        # there was an error, the factory wasn't added so this shouldn't raise
+        self.sourcelist.addUri(uri)



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