Coverage for coherence/upnp/core/service.py : 64%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php
# Copyright (C) 2006 Fluendo, S.A. (www.fluendo.com). # Copyright 2006, Frank Scholz <coherence@beebits.net>
global subscribers
subscribers.pop(service.get_sid(), None)
event_sub_url, presentation_url, scpd_url, device):
control_url.encode('ascii') if control_url else None event_sub_url if isinstance(event_sub_url, bytes) else \ event_sub_url.encode('ascii') if event_sub_url else None presentation_url if isinstance(presentation_url, bytes) else \ presentation_url.encode('ascii') if presentation_url else None scpd_url.encode('ascii') if scpd_url else None
self.device.friendly_name, self.service_type, self.id))
attribute[0].decode('utf-8') v1 = attribute[0]() v1 = getattr(self, a0) else: return elif hasattr(self, attribute[1]): v2 = getattr(self, attribute[1]) else: v2 = attribute[1] return else: elif hasattr(self, attribute): v = getattr(self, attribute) else: v = attribute except Exception as e: self.error('Service.as_tuples.append: %r' % e) import traceback self.debug(traceback.format_exc())
(self.device.get_location(), self.device.get_location()))) self.scpd_url, lambda: self.device.make_fullyqualified( self.scpd_url))) self.control_url, lambda: self.device.make_fullyqualified( self.control_url), False)) self.event_sub_url, lambda: self.device.make_fullyqualified( self.event_sub_url), False))
# def __del__(self): # print "Service deleted" # pass
self.service_type, self.id) self.event_connection.teardown() self.unsubscribe() del variables[instance]
self.service_type, int(timeout)) self.info("reset renew subscription call for %s/%s to %d", self.device.friendly_name, self.service_type, int(self.timeout) - 30) int(self.timeout) - 30, self.renew_subscription) self.device.friendly_name, self.service_type, int(self.timeout) - 30)
self.device.friendly_name, self.service_type, sid)
except KeyError: return None # not implemented
# global subscribers # subscribers[self.get_sid()] = self
def remove_it(r, sid): self.debug("remove subscription for %s", self.id) unsubscribe(self) self.subscription_id = None # global subscribers # if subscribers.has_key(sid): # del subscribers[sid]
self.debug("unsubscribe %s", self.id) d = event.unsubscribe(self) d.addCallback(remove_it, self.get_sid()) return d
signal=False): variable = self.get_state_variable(var_name) if variable: if callback is not None: if signal: callback(variable) louie.connect( callback, signal='Coherence.UPnP.StateVariable.%s.changed' % var_name, sender=self) else: variable.subscribe(callback)
self.info("renew_subscription") event.subscribe(self)
if var_name == 'LastChange': self.info("we have a LastChange event") self.get_state_variable(var_name, 0).update(var_value) tree = etree.fromstring(var_value) namespace_uri, tag = tree.tag[1:].split("}", 1) for instance in tree.findall('{%s}InstanceID' % namespace_uri): instance_id = instance.attrib['val'] self.info("instance_id %r %r", instance, instance_id) for var in instance.getchildren(): self.info("var %r", var) namespace_uri, tag = var.tag[1:].split("}", 1) self.info("%r %r %r", namespace_uri, tag, var.attrib['val']) self.get_state_variable(tag, instance_id).update( var.attrib['val']) self.info("updated var %r", var) if len(var.attrib) > 1: self.info("Extended StateVariable %s - %r", var.tag, var.attrib) if 'channel' in var.attrib and \ var.attrib['channel'] != 'Master': # TODO handle attributes that # them selves have multiple instances self.info( "Skipping update to %s " "its not for master channel %s", var.tag, var.attrib) pass else: if not self.get_state_variables(instance_id): # TODO Create instance ? self.error( "%r update failed " "(not self.get_state_variables" "(instance_id)) %r", self, instance_id) elif tag not in self.get_state_variables( instance_id): # TODO Create instance StateVariable? # SONOS stuff self.error( "%r update failed " "(not self.get_state_variables" "(instance_id).has_key(tag)) %r", self, tag) else: val = None if 'val' in var.attrib: val = var.attrib['val'] # self.debug("%r update %r %r %r", # self,namespace_uri, # tag, var.attrib['val']) self.get_state_variable( tag, instance_id).update( var.attrib['val']) self.debug("updated 'attributed' var %r", var) louie.send( 'Coherence.UPnP.DeviceClient.Service.Event.processed', None, self, (var_name, var_value, event.raw)) else: self.get_state_variable(var_name, 0).update(var_value) louie.send( 'Coherence.UPnP.DeviceClient.Service.Event.processed', None, self, (var_name, var_value, event.raw)) # The clients (e.g. media_server_client) check for last time # to detect whether service detection is complete so we need to # set it here and now to avoid a potential race condition sender=self.device, service=self) 'Coherence.UPnP.DeviceClient.Service.notified for ' '{}'.format(self))
else: 'Invalid service description received from {}: {}'.format( self.get_scpd_url(), e)) # self.debug('processPage tree is: {}'.format(tree))
# self.debug('\t->processing action: {}'.format(name)) '{%s}relatedStateVariable' % ns) arg_state_var)) arguments)
""" we need to ignore this, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """ self, name, 'n/a', instance, send_events, data_type, values) """ we need to do this here, as there we don't get there our {urn:schemas-beebits-net:service-1-0}X_withVendorDefines attibute there """
# print('service parse:', self, self.device) sender=self.device, device=self.device) 'Coherence.UPnP.Service.detection_completed for' ' {}'.format(self)) if (self.last_time_updated == None): if( self.id.endswith('AVTransport') or self.id.endswith('RenderingControl')): louie.send('Coherence.UPnP.DeviceClient.Service.notified', sender=self.device, service=self) self.last_time_updated = time.time() """
self.warning('error requesting {}'.format(url)) self.info('failure {}'.format(failure)) louie.send('Coherence.UPnP.Service.detection_failed', self.device, device=self.device)
[self.get_scpd_url()], None)
{'urn:schemas-upnp-org:service:AVTransport:2': ['LastChange'], 'urn:schemas-upnp-org:service:AVTransport:1': ['LastChange'], 'urn:schemas-upnp-org:service:ContentDirectory:2': ['SystemUpdateID', 'ContainerUpdateIDs'], 'urn:schemas-upnp-org:service:ContentDirectory:1': ['SystemUpdateID', 'ContainerUpdateIDs'], 'urn:schemas-upnp-org:service:RenderingControl:2': ['LastChange'], 'urn:schemas-upnp-org:service:RenderingControl:1': ['LastChange'], 'urn:schemas-upnp-org:service:ScheduledRecording:1': ['LastChange'], }
'[version: {}, backend: {}]'.format(id, version, backend))
self.namespace, id, int(self.version))
self.event_metadata = 'urn:schemas-upnp-org:metadata-1-0/AVT/' self.event_metadata = 'urn:schemas-upnp-org:metadata-1-0/RCS/'
self.last_change = self._variables[0]['LastChange'] self.subscription_url)) self.subscription_url, EventSubscriptionServer(self)) self.subscription_url))
self.check_moderated_variables)
p.disconnect()
try: return self._actions[action_name] except KeyError: return None # not implemented
del self._pending_notifications[d]
notify = [] for vdict in list(self._variables.values()): notify += [v for v in list(vdict.values()) if v.send_events]
self.info("new_subscriber %s %s", subscriber, notify) if len(notify) <= 0: return
root = etree.Element('{%s}propertyset' % NS_UPNP_ORG_EVENT_1_0, nsmap={'e': NS_UPNP_ORG_EVENT_1_0}) evented_variables = 0 for n in notify: e = etree.SubElement(root, '{%s}property' % NS_UPNP_ORG_EVENT_1_0) if n.name == 'LastChange': if subscriber['seq'] == 0: text = self.build_last_change_event(n.instance, force=True) else: text = self.build_last_change_event(n.instance) if text is not None: etree.SubElement(e, n.name).text = text evented_variables += 1 else: etree.SubElement(e, n.name).text = str(n.value) evented_variables += 1
if evented_variables > 0: xml = etree.tostring(root, encoding='utf-8', pretty_print=True) d, p = event.send_notification(subscriber, xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification, d) self._subscribers[subscriber['sid']] = subscriber
self._variables[instance] = {} for v in list(self._variables[0].values()): self._variables[instance][v.name] = variable.StateVariable( v.service, v.name, v.implementation, instance, v.send_events, v.data_type, v.allowed_values) self._variables[instance][ v.name].has_vendor_values = v.has_vendor_values self._variables[instance][v.name].default_value = v.default_value # self._variables[instance][v.name].value = v.default_value # FIXME self._variables[instance][v.name].old_value = v.old_value self._variables[instance][v.name].value = v.value self._variables[instance][ v.name].dependant_variable = v.dependant_variable
if instance == 0: return del (self._variables[instance])
self._subscribers) > 0: xml = self.build_single_notification(instance, variable_name, variable.value) for s in list(self._subscribers.values()): d, p = event.send_notification(s, xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification, d)
value.addCallback(process_value) else: except KeyError: pass
try: return self._variables[int(instance)][variable_name] except KeyError: return None
nsmap={'e': NS_UPNP_ORG_EVENT_1_0})
if variable.name != 'LastChange' and \ variable.name[0:11] != 'A_ARG_TYPE_' and \ not variable.never_evented: if variable.updated or force: s = etree.SubElement(e, variable.name) s.attrib['val'] = str(variable.value) variable.updated = False got_one = True if variable.dependant_variable is not None: dependants = variable.dependant_variable.\ get_allowed_values() if dependants is not None and len(dependants) > 0: s.attrib['channel'] = dependants[0] return etree.tostring(root, encoding='utf-8', pretty_print=True) else:
if len(notify) <= 0: return
root = etree.Element('{%s}propertyset' % NS_UPNP_ORG_EVENT_1_0, nsmap={'e': NS_UPNP_ORG_EVENT_1_0})
if isinstance(notify, variable.StateVariable): notify = [notify, ]
evented_variables = 0 for n in notify: e = etree.SubElement(root, '{%s}property' % NS_UPNP_ORG_EVENT_1_0) if n.name == 'LastChange': text = self.build_last_change_event(instance=n.instance) if text is not None: etree.SubElement(e, n.name).text = text evented_variables += 1 else: s = etree.SubElement(e, n.name).text = str(n.value) evented_variables += 1 if n.dependant_variable is not None: dependants = n.dependant_variable.get_allowed_values() if dependants is not None and len(dependants) > 0: e.attrib['channel'] = dependants[0]
if evented_variables == 0: return xml = etree.tostring(root, encoding='utf-8', pretty_print=True)
for s in list(self._subscribers.values()): d, p = event.send_notification(s, xml) self._pending_notifications[d] = p d.addBoth(self.rm_notification, d)
for s in list(self._subscribers.values()): timeout = 86400 if s['timeout'].startswith('Second-'): timeout = int(s['timeout'][len('Second-'):]) if time.time() > s['created'] + timeout: del s
# print "check_moderated for %s" % self.id # print self._subscribers if len(self._subscribers) <= 0: return variables = moderated_variables[self.get_type()] notify = [] for v in variables: # print self._variables[0][v].name, self._variables[0][v].updated for vdict in list(self._variables.values()): if vdict[v].updated: vdict[v].updated = False notify.append(vdict[v]) self.propagate_notification(notify)
self.info("simulate_notification for {}".format(self.id)) self.set_variable(0, 'CurrentConnectionIDs', '0')
if self.scpdXML is None: self.scpdXML = scpdXML(self) self.scpdXML = self.scpdXML.build_xml() return self.scpdXML
instance=0, evented='no', data_type='string', dependant_variable=None, default_value=None, allowed_values=None, has_vendor_values=False, allowed_value_range=None, moderated=False): """ enables a backend to add an own, vendor defined, StateVariable to the service
@ivar name: the name of the new StateVariable @ivar implementation: either 'optional' or 'required' @ivar instance: the instance number of the service that variable should be assigned to, usually '0' @ivar evented: boolean, or the special keyword 'never' if the variable doesn't show up in a LastChange event too @ivar data_type: 'string','boolean','bin.base64' or various number formats @ivar dependant_variable: the name of another StateVariable that depends on this one @ivar default_value: the value this StateVariable should have by default when created for another instance of in the service @ivar allowed_values: a C{list} of values this StateVariable can have @ivar has_vendor_values: boolean if there are values outside the allowed_values list too @ivar allowed_value_range: a C{dict} of 'minimum','maximum' and 'step' values @ivar moderated: boolean, True if this StateVariable should only be evented via a LastChange event
"""
# FIXME # we should raise an Exception when there as a # StateVariable with that name already
if evented == 'never': send_events = 'no' else: send_events = evented new_variable = variable.StateVariable(self, name, implementation, instance, send_events, data_type, allowed_values) if default_value is None: new_variable.default_value = '' else: new_variable.default_value = \ new_variable.old_value = new_variable.value = default_value
new_variable.dependant_variable = dependant_variable new_variable.has_vendor_values = has_vendor_values new_variable.allowed_value_range = allowed_value_range new_variable.moderated = moderated if evented == 'never': new_variable.never_evented = True self._variables.get(instance)[name] = new_variable return new_variable
needs_callback=True): """ enables a backend to add an own, vendor defined, Action to the service
@ivar name: the name of the new Action @ivar implementation: either 'optional' or 'required' @ivar arguments: a C{list} if argument C{tuples}, like (name,direction,relatedStateVariable) @ivar needs_callback: this Action needs a method in the backend or service class """ # FIXME - we should raise an Exception when there as an Action # with that name already we should raise an Exception when there is no # related StateVariable for an Argument
""" check for action in backend """ callback = getattr(self.backend, "upnp_%s" % name, None)
if callback is None: """ check for action in ServiceServer """ callback = getattr(self, "upnp_%s" % name, None)
if needs_callback and callback is None: """ we have one or more 'A_ARG_TYPE_' variables issue a warning for now """ if implementation == 'optional': self.info( '%s has a missing callback for %s action %s, ' 'action disabled', self.id, implementation, name) return else: if (hasattr(self, 'implementation') and self.implementation == 'required') or not hasattr( self, 'implementation'): self.warning( '%s has a missing callback for %s action %s, ' 'service disabled', self.id, implementation, name) raise LookupError("missing callback")
arguments_list = [] for argument in arguments: arguments_list.append( action.Argument(argument[0], argument[1].lower(), argument[2]))
new_action = action.Action(self, name, implementation, arguments_list) self._actions[name] = new_action if callback is not None: new_action.set_callback(callback) self.info('Add callback %s for %s/%s', callback, self.id, name) return new_action
__file__, os.path.join('xml-service-descriptions', '%s%d.xml' % (self.id, int(self.version))))
'{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) is not None: needs_callback = True 'Optional').attrib.get( '{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) is not None or action_node.attrib.get( '{urn:schemas-beebits-net:service-1-0}X_needs_backend', None) is not None:
action.Argument(arg_name, arg_direction, arg_state_var)) 0:11] == 'A_ARG_TYPE_' and arg_direction == 'out':
""" check for action in backend """
""" check for action in ServiceServer """
""" we have one or more 'A_ARG_TYPE_' variables issue a warning for now """ '%s has a missing callback for %s action %s, ' 'action disabled', self.id, implementation, name) else: self.implementation == 'required') or \ not hasattr(self, 'implementation'): self.warning( '%s has a missing callback for %s action %s, ' 'service disabled', self.id, implementation, name)
"vendor_value_defaults", None) service_value_defaults = backend_vendor_value_defaults.get(self.id, None)
"vendor_range_defaults", None) service_range_defaults = backend_vendor_range_defaults.get(self.id)
variable.StateVariable(self, name, implementation, instance, send_events, data_type, values)
'{urn:schemas-beebits-net:service-1-0}X_dependantVariable') self._variables.get(instance)[ name].dependant_variable = dependant_variable default_value) 'sendEventsAttribute').attrib.get( '{urn:schemas-beebits-net:service-1-0}X_no_means_never', None) self._variables.get(instance)[name].set_never_evented( never_evented)
'{urn:schemas-beebits-net:service-1-0}X_withVendorDefines', None) variable_value_defaults = service_value_defaults.get(name, None) if variable_value_defaults: self.info("overwriting %s default value with %s", name, variable_value_defaults) self._variables.get(instance)[name].set_allowed_values( variable_value_defaults)
self._variables.get( instance)[name].has_vendor_values = True
vendor_values = \ allowed_value_range.attrib.get( '{urn:schemas-beebits-net:service-1-0}' 'X_withVendorDefines', None) range = {} for e in list(allowed_value_range): range[e.tag] = e.text if vendor_values is not None: if service_range_defaults: variable_range_defaults = \ service_range_defaults.get(name) if (variable_range_defaults is not None and variable_range_defaults.get( e.tag) is not None): self.info( "overwriting %s attribute %s with %s", name, e.tag, str(variable_range_defaults[e.tag])) range[e.tag] = variable_range_defaults[e.tag] elif e.text is None: self.info( "missing vendor definition for %s, " "attribute %s", name, e.tag) self._variables.get(instance)[name].set_allowed_value_range( **range) if vendor_values is not None: self._variables.get( instance)[name].has_vendor_values = True variable_range_defaults = service_range_defaults.get(name) if variable_range_defaults is not None: self._variables.get( instance)[name].set_allowed_value_range( **variable_range_defaults) self._variables.get( instance)[name].has_vendor_values = True
v.dependant_variable = self._variables.get(instance).get( v.dependant_variable)
nsmap={None: 'urn:schemas-upnp-org:service-1-0'})
argument.get_direction() argument.get_state_variable()
else:
var.allowed_value_range) > 0: complete = True for name, value in list(var.allowed_value_range.items()): if value is None: complete = False if complete: avl = etree.SubElement(s, 'allowedValueRange') for name, value in list(var.allowed_value_range.items()): if value is not None: etree.SubElement(avl, name).text = str(value)
pretty_print=True)
""" check for out arguments if yes: check if there are related ones to StateVariables with non A_ARG_TYPE_ prefix if yes: check if there is a call plugin method for this action if yes: update StateVariable values with call result if no: get StateVariable values and add them to result dict """ # print 'get_action_results', action, instance # print 'get_state_variable_contents', argument.name self.variables[instance][ # pylint: disable=no-member argument.get_state_variable()] not variable.moderated: notify.append(variable) else: variable = \ self.variables[instance][ # pylint: disable=no-member argument.get_state_variable()] r[argument.name] = variable.value notify)
return r
""" generic UPnP service control method, which will be used if no soap_ACTIONNAME method in the server service control class can be found """ kwargs['soap_methodName']] except KeyError: return failure.Failure(errorCode(401))
# self.debug("\t- action.name %r", action.name) kwargs['X_UPnPClient'] == 'XBox'): 'ContainerID' in kwargs): """ XXX: THIS IS SICK """ kwargs['ObjectID'] = kwargs['ContainerID'] del kwargs['ContainerID']
else: self.critical('argument %s not valid for action %s', arg_name, action.name) return failure.Failure(errorCode(402)) self.critical('argument %s missing for action %s', [a.get_name() for a in in_arguments], action.name) return failure.Failure(errorCode(402))
# self.debug('callit args', args) # self.debug('callit kwargs', kwargs) # self.debug('callit callback', callback) return result
# print 'failure', x
# call plugin method for this action |