Index: flumotion/wizard/steps.py =================================================================== --- flumotion/wizard/steps.py (revision 1119) +++ flumotion/wizard/steps.py (working copy) @@ -136,14 +136,20 @@ def _checkChannels(device): + # check channels on a v4lsrc element with the given device + # returns: a deferred returning a tuple of (deviceName, channels, norms) + # or a failure from twisted.internet import defer, reactor import gst import gst.interfaces - d = defer.Deferred() - + class Result: + def __init__(self): + self.d = defer.Deferred() + self.returned = False + # gst, defer and errors are already in the namespace - def state_changed_cb(pipeline, old, new): + def state_changed_cb(pipeline, old, new, res): if not (old == gst.STATE_NULL and new == gst.STATE_READY): return element = pipeline.get_by_name('source') @@ -151,18 +157,23 @@ channels = [channel.label for channel in element.list_channels()] norms = [norm.label for norm in element.list_norms()] reactor.callLater(0, pipeline.set_state, gst.STATE_NULL) - d.callback((deviceName, channels, norms)) + if not res.returned: + res.returned = True + res.d.callback((deviceName, channels, norms)) - def error_cb(pipeline, element, error, _): - d.errback(errors.UnknownDeviceError("The device does not exist")) + def error_cb(pipeline, element, error, _, res): + if not res.returned: + res.returned = True + res.d.errback(errors.GstError(error.message)) pipeline = 'v4lsrc name=source device=%s ! fakesink' % device bin = gst.parse_launch(pipeline) - bin.connect('state-change', state_changed_cb) - bin.connect('error', error_cb) + result = Result() + bin.connect('state-change', state_changed_cb, result) + bin.connect('error', error_cb, result) bin.set_state(gst.STATE_PLAYING) - return d + return result.d class TVCard(VideoSource): @@ -196,6 +207,11 @@ self.combobox_channel.set_list(channels) self.combobox_channel.set_sensitive(True) + def _queryGstErrorErrback(self, failure): + failure.trap(errors.GstError) + self.clear_combos() + self.wizard.error_dialog('GStreamer error: %s' % failure.value) + def _unknownDeviceErrback(self, failure): failure.trap(errors.UnknownDeviceError) self.clear_combos() @@ -204,8 +220,9 @@ self.wizard.block_next(True) device = self.combobox_device.get_string() - d = self.run_on_worker(_checkChannels, device) + d = self.workerRun(_checkChannels, device) d.addCallback(self._queryCallback) + d.addErrback(self._queryGstErrorErrback) d.addErrback(self._unknownDeviceErrback) def get_component_properties(self): @@ -229,31 +246,42 @@ wizard.register_step(FireWire) +# FIXME: rename, only for v4l stuff def _checkDeviceName(device): + # this function gets sent to and executed on the worker + # it will fire a deferred returning the deviceName, or a failure from twisted.internet import defer, reactor import gst + + class Result: + def __init__(self): + self.d = defer.Deferred() + self.returned = False - d = defer.Deferred() - # gst, defer and errors are already in the namespace - def state_changed_cb(pipeline, old, new): + def state_changed_cb(pipeline, old, new, res): if not (old == gst.STATE_NULL and new == gst.STATE_READY): return element = pipeline.get_by_name('source') - deviceName = elementget_property('device-name') + deviceName = element.get_property('device-name') reactor.callLater(0, pipeline.set_state, gst.STATE_NULL) - d.callback(deviceName) + if not res.returned: + res.returned = True + res.d.callback(deviceName) - def error_cb(pipeline, element, error, _): - d.errback(errors.UnknownDeviceError("The device does not exist")) + def error_cb(pipeline, element, error, _, res): + if not res.returned: + res.returned = True + res.d.errback(errors.GstError(error.message)) pipeline = 'v4lsrc name=source device=%s autoprobe=false ! fakesink' % device bin = gst.parse_launch(pipeline) - bin.connect('state-change', state_changed_cb) - bin.connect('error', error_cb) + result = Result() + bin.connect('state-change', state_changed_cb, result) + bin.connect('error', error_cb, result) bin.set_state(gst.STATE_PLAYING) - return d + return result.d class Webcam(VideoSource): @@ -267,7 +295,8 @@ self.in_setup = True self.combobox_device.set_list(('/dev/video0', '/dev/video1', - '/dev/video2')) + '/dev/video2', + '/dev/video3')) self.in_setup = False def on_combobox_device_changed(self, combo): @@ -293,10 +322,15 @@ self.spinbutton_height.set_sensitive(True) self.spinbutton_framerate.set_sensitive(True) + def _queryGstErrorErrback(self, failure): + failure.trap(errors.GstError) + self.clear() + self.wizard.error_dialog('GStreamer error: %s' % failure.value) + def _unknownDeviceErrback(self, failure): failure.trap(errors.UnknownDeviceError) self.clear() - + def update(self): if self.in_setup: return @@ -304,8 +338,9 @@ self.wizard.block_next(True) device = self.combobox_device.get_string() - d = self.run_on_worker(_checkDeviceName, device) + d = self.workerRun(_checkDeviceName, device) d.addCallback(self._queryCallback) + d.addErrback(self._queryGstErrorErrback) d.addErrback(self._unknownDeviceErrback) wizard.register_step(Webcam) @@ -394,28 +429,36 @@ import gst import gst.interfaces - d = defer.Deferred() - + class Result: + def __init__(self): + self.d = defer.Deferred() + self.returned = False + # gst, defer and errors are already in the namespace - def state_changed_cb(pipeline, old, new): + def state_changed_cb(pipeline, old, new, res): if not (old == gst.STATE_NULL and new == gst.STATE_READY): return element = pipeline.get_by_name('source') deviceName = element.get_property('device-name') tracks = [track.label for track in element.list_tracks()] reactor.callLater(0, pipeline.set_state, gst.STATE_NULL) - d.callback((deviceName, tracks)) + if not res.returned: + res.returned = True + res.d.callback((deviceName, tracks)) - def error_cb(pipeline, element, error, _): - d.errback(errors.GstError(error.message)) + def error_cb(pipeline, element, error, _, res): + if not res.returned: + res.returned = True + res.d.errback(errors.GstError(error.message)) pipeline = '%s name=source device=%s ! fakesink' % (source_element, device) bin = gst.parse_launch(pipeline) - bin.connect('state-change', state_changed_cb) - bin.connect('error', error_cb) + result = Result() + bin.connect('state-change', state_changed_cb, result) + bin.connect('error', error_cb, result) bin.set_state(gst.STATE_PLAYING) - return d + return result.d class Soundcard(wizard.WizardStep): @@ -471,11 +514,6 @@ self.combobox_input.set_list(tracks) self.combobox_input.set_sensitive(True) - def _gstErrback(self, failure): - failure.trap(errors.GstError) - self.clear_combos() - self.wizard.error_dialog('Gstreamer error: %s' % failure.value) - def update_devices(self): self.block_update = True enum = self.combobox_system.get_enum() @@ -487,6 +525,12 @@ raise AssertionError self.block_update = False + # FIXME: move higher up in hierarchy + def _queryGstErrorErrback(self, failure): + failure.trap(errors.GstError) + self.clear_combos() + self.wizard.error_dialog('GStreamer error: %s' % failure.value) + def update_inputs(self): if self.block_update: return @@ -494,11 +538,11 @@ enum = self.combobox_system.get_enum() device = self.combobox_device.get_string() - d = self.run_on_worker(_checkTracks, enum.element, device) + d = self.workerRun(_checkTracks, enum.element, device) d.addCallback(self._queryCallback) + d.addErrback(self._queryGstErrorErrback) #d.addErrback(self._unknownDeviceErrback) #d.addErrback(self._permissionDeniedErrback) - d.addErrback(self._gstErrback) def get_component_properties(self): channels = self.combobox_channels.get_enum() Index: flumotion/wizard/wizard.py =================================================================== --- flumotion/wizard/wizard.py (revision 1119) +++ flumotion/wizard/wizard.py (working copy) @@ -31,7 +31,7 @@ from twisted.internet import defer from flumotion.configure import configure -from flumotion.common import log +from flumotion.common import log, errors from flumotion.utils.gstutils import gsignal from flumotion.wizard import enums, save @@ -303,18 +303,24 @@ def get_section(self): return getattr(self, 'section', '') - def run_on_worker(self, function, *args): + def workerRun(self, function, *args): + """ + Run the given function and arguments on the selected worker. + + @returns: L{twisted.internet.defer.Deferred} + """ admin = self.wizard.get_admin() worker = self.worker - d = defer.fail() if not admin: - print 'skipping query, no admin' - elif not worker: - print 'skipping query, no worker' - else: - d = admin.workerRun(worker, function, *args) - return d + self.warning('skipping workerRun, no admin') + return defer.fail(errors.FlumotionError('no admin')) + + if not worker: + self.warning('skipping workerRun, no worker') + return defer.fail(errors.FlumotionError('no worker')) + + return admin.workerRun(worker, function, *args) def get_next(self): """ @@ -353,7 +359,7 @@ def worker_changed(self): pass - + class Wizard(gobject.GObject, log.Loggable): sidebar_color = gtk.gdk.color_parse('#9bc6ff') @@ -511,13 +517,13 @@ box.show() self.combobox_worker = gtk.combo_box_new_text() - self.combobox_worker.connect('changed', - self._combobox_worker_changed, step) - box.pack_start(self.combobox_worker, False, False, 6) map(self.combobox_worker.append_text, self._workers) self.combobox_worker.set_active(self._last_worker) + self.combobox_worker.connect('changed', + self._combobox_worker_changed, step) + self.combobox_worker.show() def show_previous(self): Index: flumotion/admin/admin.py =================================================================== --- flumotion/admin/admin.py (revision 1119) +++ flumotion/admin/admin.py (working copy) @@ -257,15 +257,14 @@ remote_methodName on the worker - @returns: deferred + @rtype: L{twisted.internet.defer.Deferred} """ r = common.argRepr(args, kwargs, max=20) - self.info('calling remote method %s(%s) on worker %s' % (methodName, r, + self.debug('calling remote method %s(%s) on worker %s' % (methodName, r, workerName)) d = self.remote.callRemote('workerCallRemote', workerName, methodName, *args, **kwargs) d.addErrback(self._workerCallRemoteErrback, methodName) - d.addErrback(self._defaultErrback) return d def _workerCallRemoteErrback(self, failure, methodName): @@ -276,9 +275,16 @@ ## Worker methods def checkElements(self, workerName, elements): - return self.workerCallRemote(workerName, 'checkElements', elements) + d = self.workerCallRemote(workerName, 'checkElements', elements) + d.addErrback(self._defaultErrback) + return d def workerRun(self, workerName, function, *args, **kwargs): + """ + Run the given function and args on the given worker. + + @rtype: L{twisted.internet.defer.Deferred} + """ import inspect if not callable(function): raise TypeError, "need a callable" @@ -286,7 +292,7 @@ try: source = inspect.getsource(function) except IOError: - return defer.fail() + return defer.fail(errors.FlumotionError('Could not find source')) functionName = function.__name__ return self.workerCallRemote(workerName, 'runCode', source, Index: flumotion/common/errors.py =================================================================== --- flumotion/common/errors.py (revision 1119) +++ flumotion/common/errors.py (working copy) @@ -59,9 +59,12 @@ class UnknownComponentError(pb.Error): "A given component or component type does not exist" +class FlumotionError(pb.Error): + "Generic Flumotion error" + # GStreamer errors class GstError(pb.Error): - "Generic Gstreamer error" + "Generic GStreamer error" class UnknownDeviceError(pb.Error): "The device does not exist" @@ -70,4 +73,4 @@ "Permission denied" class DeviceBusyError(GstError): - "Generic Gstreamer error" + "Generic GStreamer error"