# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php
# Copyright 2009 - Frank Scholz <coherence@beebits.net>
""" TUBE service classes
"""
import urllib.error
import urllib.parse
import urllib.parse
import urllib.request
import dbus
from lxml import etree
from twisted.internet import defer
from twisted.python.util import OrderedDict
from twisted.web import resource
from twisted.python import failure
from coherence import log
from coherence.upnp.core import DIDLLite
from coherence.upnp.core import action
from coherence.upnp.core import service
from coherence.upnp.core import variable
from coherence.upnp.core.soap_service import UPnPPublisher
from coherence.upnp.core.utils import ReverseProxyUriResource
from coherence.upnp.devices.media_server import RootDeviceXML
from coherence.upnp.devices.basics import DeviceHttpRoot
from coherence.upnp.core.soap_service import errorCode
from .upnp.core import xml_constants
[docs]class MirabeauProxy(resource.Resource, log.LogAble):
logCategory = 'mirabeau'
def __init__(self):
resource.Resource.__init__(self)
log.LogAble.__init__(self)
self.isLeaf = 0
[docs] def getChildWithDefault(self, path, request):
self.info('MiraBeau getChildWithDefault %s, %s, %s %s', request.method,
path, request.uri, request.client)
uri = urllib.parse.unquote_plus(path)
self.info('MiraBeau uri %r', uri)
return ReverseProxyUriResource(uri)
[docs]class TubeServiceControl(UPnPPublisher):
logCategory = 'mirabeau'
def __init__(self, server):
UPnPPublisher.__init__(self)
self.service = server
self.variables = server.get_variables()
self.actions = server.get_actions()
[docs] def get_action_results(self, result, action, instance):
""" 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
"""
self.debug('get_action_results %s', result)
# print 'get_action_results', action, instance
notify = []
for argument in action.get_out_arguments():
# print 'get_state_variable_contents', argument.name
if argument.name[0:11] != 'A_ARG_TYPE_':
variable = self.variables[instance][
argument.get_state_variable()]
variable.update(
result[argument.name].decode('utf-8').encode('utf-8'))
# print('update state variable contents',
# variable.name, variable.value, variable.send_events)
if (variable.send_events == 'yes' and
variable.moderated is False):
notify.append(variable)
self.service.propagate_notification(notify)
self.info('action_results unsorted %s %s', action.name, result)
if len(result) == 0:
return result
ordered_result = OrderedDict()
for argument in action.get_out_arguments():
if action.name == 'XXXBrowse' and argument.name == 'Result':
didl = DIDLLite.DIDLElement.fromString(
result['Result'].decode('utf-8'))
changed = False
for item in didl.getItems():
new_res = DIDLLite.Resources()
for res in item.res:
remote_protocol, remote_network, \
remote_content_format, _ = \
res.protocolInfo.split(':')
if remote_protocol == 'http-get' and \
remote_network == '*':
quoted_url = urllib.parse.quote_plus(res.data)
print("modifying", res.data)
res.data = urllib.parse.urlunsplit(
('http',
self.service.device.external_address,
'mirabeau',
quoted_url, ""))
print("--->", res.data)
new_res.append(res)
changed = True
item.res = new_res
if changed:
didl.rebuild()
ordered_result[argument.name] = \
didl.toString() # .replace('<ns0:','<')
else:
ordered_result[argument.name] = result[
argument.name].decode('utf-8')
else:
ordered_result[argument.name] = result[argument.name].decode(
'utf-8').encode('utf-8')
self.info('action_results sorted %s %s', action.name, ordered_result)
return ordered_result
[docs] def soap__generic(self, *args, **kwargs):
""" generic UPnP service control method,
which will be used if no soap_ACTIONNAME method
in the server service control class can be found
"""
try:
action = self.actions[kwargs['soap_methodName']]
except KeyError:
return failure.Failure(errorCode(401))
try:
instance = int(kwargs['InstanceID'])
except (ValueError, KeyError):
instance = 0
self.info("soap__generic %s %s %s", action, __name__, kwargs)
del kwargs['soap_methodName']
in_arguments = action.get_in_arguments()
for arg_name, arg in kwargs.items():
if arg_name.find('X_') == 0:
continue
al = [a for a in in_arguments if arg_name == a.get_name()]
if len(al) > 0:
in_arguments.remove(al[0])
else:
self.critical('argument %s not valid for action %s', arg_name,
action.name)
return failure.Failure(errorCode(402))
if len(in_arguments) > 0:
self.critical('argument %s missing for action %s',
[a.get_name() for a in in_arguments], action.name)
return failure.Failure(errorCode(402))
def got_error(x):
self.info('dbus error during call processing')
return x
# call plugin method for this action
# print 'callit args', args
# print 'callit kwargs', kwargs
# print 'callit action', action
# print 'callit dbus action', self.service.service.action
d = defer.Deferred()
self.service.service.call_action(action.name,
dbus.Dictionary(kwargs,
signature='ss'),
reply_handler=d.callback,
error_handler=d.errback,
utf8_strings=True)
d.addCallback(self.get_action_results, action, instance)
d.addErrback(got_error)
return d
[docs]class TubeServiceProxy(service.ServiceServer, resource.Resource):
logCategory = 'mirabeau'
def __init__(self, tube_service, device, backend=None):
self.device = device
self.service = tube_service
resource.Resource.__init__(self)
id = self.service.get_id().split(':')[3]
service.ServiceServer.__init__(self, id, self.device.version, None)
self.control = TubeServiceControl(self)
self.putChild(self.scpd_url, service.scpdXML(self, self.control))
self.putChild(self.control_url, self.control)
self.device.web_resource.putChild(id, self)
[docs] def init_var_and_actions(self):
""" retrieve all actions and create the Action
classes for our (proxy) server
retrieve all variables and create the StateVariable
classes for our (proxy) server
"""
xml = self.service.get_scpd_xml()
tree = etree.fromstring(xml)
ns = xml_constants.UPNP_SERVICE_NS
for action_node in tree.findall('.//{%s}action' % ns):
name = action_node.findtext('{%s}name' % ns)
arguments = []
for argument in action_node.findall('.//{%s}argument' % ns):
arg_name = argument.findtext('{%s}name' % ns)
arg_direction = argument.findtext('{%s}direction' % ns)
arg_state_var = argument.findtext(
'{%s}relatedStateVariable' % ns)
arguments.append(action.Argument(arg_name, arg_direction,
arg_state_var))
self._actions[name] = action.Action(self, name, 'n/a', arguments)
for var_node in tree.findall('.//{%s}stateVariable' % ns):
send_events = var_node.attrib.get('sendEvents', 'yes')
name = var_node.findtext('{%s}name' % ns)
data_type = var_node.findtext('{%s}dataType' % ns)
values = []
""" we need to ignore this, as there we don't get there our
{urn:schemas-beebits-net:service-1-0}X_withVendorDefines
attibute there
"""
for allowed in var_node.findall('.//{%s}allowedValue' % ns):
values.append(allowed.text)
instance = 0
self._variables.get(instance)[name] = \
variable.StateVariable(
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
"""
self._variables.get(instance)[name].has_vendor_values = True
[docs]class TubeDeviceProxy(log.LogAble):
logCategory = 'dbus'
def __init__(self, coherence, tube_device, external_address):
log.LogAble.__init__(self)
self.device = tube_device
self.coherence = coherence
self.external_address = external_address
self.uuid = self.device.get_id().split('-')
self.uuid[1] = 'tube'
self.uuid = '-'.join(self.uuid)
self.friendly_name = self.device.get_friendly_name()
self.device_type = self.device.get_friendly_device_type()
self.version = int(self.device.get_device_type_version())
self._services = []
self._devices = []
self.icons = []
self.info("uuid: %s, name: %r, device type: %r, version: %r",
self.uuid, self.friendly_name,
self.device_type, self.version)
""" create the http entrypoint """
self.web_resource = DeviceHttpRoot(self)
self.coherence.add_web_resource(str(self.uuid)[5:], self.web_resource)
""" create the Service proxy(s) """
for service in self.device.services:
self.debug("Proxying service %r", service)
new_service = TubeServiceProxy(service, self)
self._services.append(new_service)
""" create a device description xml file(s) """
version = self.version
while version > 0:
self.web_resource.putChild(
'description-%d.xml' % version,
RootDeviceXML(
self.coherence.hostname,
str(self.uuid),
self.coherence.urlbase,
device_type=self.device_type,
version=version,
friendly_name=self.friendly_name,
# model_description='Coherence UPnP %s' % self.device_type,
# model_name='Coherence UPnP %s' % self.device_type,
services=self._services,
devices=self._devices,
icons=self.icons))
version -= 1
""" and register with SSDP server """
self.register()
[docs] def register(self):
s = self.coherence.ssdp_server
uuid = str(self.uuid)
host = self.coherence.hostname
self.msg('%s register', self.device_type)
# we need to do this after the children
# are there, since we send notifies
s.register('local',
'%s::upnp:rootdevice' % uuid,
'upnp:rootdevice',
self.coherence.urlbase + uuid[5:] +
'/' + 'description-%d.xml' % self.version,
host=host)
s.register('local',
uuid,
uuid,
self.coherence.urlbase + uuid[5:] +
'/' + 'description-%d.xml' % self.version,
host=host)
version = self.version
while version > 0:
if version == self.version:
silent = False
else:
silent = True
s.register('local',
'%s::urn:schemas-upnp-org:device:%s:%d' % (
uuid, self.device_type, version),
'urn:schemas-upnp-org:device:%s:%d' % (
self.device_type, version),
self.coherence.urlbase + uuid[5:] +
'/' + 'description-%d.xml' % version,
silent=silent,
host=host)
version -= 1
for service in self._services:
device_version = self.version
service_version = self.version
if hasattr(service, 'version'):
service_version = service.version
silent = False
while service_version > 0:
try:
namespace = service.namespace
except Exception:
namespace = 'schemas-upnp-org'
device_description_tmpl = 'description-%d.xml' % device_version
if hasattr(service, 'device_description_tmpl'):
device_description_tmpl = service.device_description_tmpl
s.register('local',
'%s::urn:%s:service:%s:%d' % (
uuid, namespace, service.id, service_version),
'urn:%s:service:%s:%d' % (
namespace, service.id, service_version),
self.coherence.urlbase + uuid[5:] +
'/' + device_description_tmpl,
silent=silent,
host=host)
silent = True
service_version -= 1
device_version -= 1