Package brisa :: Package upnp :: Package device :: Module service
[hide private]
[frames] | no frames]

Source Code for Module brisa.upnp.device.service

  1  # Licensed under the MIT license 
  2  # http://opensource.org/licenses/mit-license.php or see LICENSE file. 
  3  # Copyright 2007-2008 Brisa Team <brisa-develop@garage.maemo.org> 
  4   
  5  """ Device-side service implementation. Used for implementing and deploying 
  6  UPnP services. 
  7  """ 
  8   
  9  from os import path, mkdir 
 10   
 11  from brisa.core import log, config, failure, webserver 
 12    
 13  from brisa.upnp.base_service import BaseService, BaseStateVariable 
 14  from brisa.upnp.base_service_builder import BaseServiceBuilder 
 15  from brisa.upnp.device.action import Action, Argument 
 16  from brisa.upnp.device.event import EventController 
 17  from brisa.upnp.device.xml_gen import ServiceXMLBuilder 
 18  from brisa.upnp import soap 
 19   
 20   
21 -class ErrorCode(Exception):
22 """ Wrapper for an error code. Contains a status attribute that corresponds 23 with the error code. 24 """ 25
26 - def __init__(self, status):
27 self.status = status
28 29
30 -class InvalidService(Exception):
31 pass
32 33
34 -class StateVariable(BaseStateVariable):
35
36 - def __init__(self, service, name, send_events, multicast, data_type, values=[]):
37 BaseStateVariable.__init__(self, service, name, send_events, 38 multicast, data_type, values)
39 40
41 -class ServiceBuilder(BaseServiceBuilder):
42
43 - def build(self):
44 try: 45 return BaseServiceBuilder.build(self) 46 except: 47 raise InvalidService('Invalid scpd.xml')
48
49 - def _create_argument(self, arg_name, arg_direction, arg_state_var):
50 return Argument(arg_name, arg_direction, arg_state_var)
51
52 - def _create_action(self, name, args):
53 """ Creates an action and sets it run function to soap_ActionName(). """ 54 action = Action(self.service, name, args) 55 action.run_function = getattr(self.service, "soap_%s" % name, None) 56 return action
57
58 - def _create_state_var(self, name, send_events, multicast, 59 data_type, values):
60 return StateVariable(self.service, name, send_events, 61 multicast, data_type, values)
62 63
64 -class ServiceController(webserver.CustomResource):
65 """ Wrapper for receiving soap calls and assigning them to correspondent 66 methods. Extend UPnPPublisher and add the class to the web server as a 67 resource and your methods will be exported. 68 """ 69 encoding = "UTF-8" 70
71 - def __init__(self, service, service_type):
72 webserver.CustomResource.__init__(self, 'control') 73 self.service = service 74 self.service_type = service_type
75
76 - def render(self, uri, request, response):
77 """ Renders a request received. 78 """ 79 data = request.read() 80 headers = request.headers 81 82 method_name, args, kwargs, ns = soap.parse_soap_call(data) 83 try: 84 headers['content-type'].index('text/xml') 85 except: 86 # Builds error if we don't have an content-type field with xml 87 return self._build_error(failure.Failure(ErrorCode(415)), request, 88 method_name, response) 89 90 function = self.lookup_function(method_name) 91 92 if not function: 93 return self._method_not_found(request, response, method_name) 94 else: 95 return self._get_call_response(request, response, method_name, 96 function, *args, **kwargs) 97 98 return ['']
99
100 - def lookup_function(self, function_name):
101 """ Lookup published SOAP function. 102 """ 103 log.info('Finding service action %s' % function_name) 104 for action_name, action in self.service._actions.iteritems(): 105 if action_name == function_name: 106 return action 107 log.info('Action %s not founded' % function_name) 108 return None
109
110 - def _get_call_response(self, request, response_obj, method_name, 111 function, *args, **kwargs):
112 """ Performs the soap call, builds and returns a response. 113 """ 114 result = function(*args, **kwargs) 115 116 ns = self.service_type 117 try: 118 method = result.keys()[0] 119 result = result[method] 120 except AttributeError, IndexError: 121 result = {} 122 method = '' 123 response = soap.build_soap_call("{%s}%s" % (ns, method), 124 result, encoding=None) 125 return self._build_response(request, response, response_obj)
126
127 - def _build_error(self, failure, request, method_name, response_obj):
128 """ Builds an error based on the failure code. 129 """ 130 e = failure.value 131 status = 500 132 133 if isinstance(e, ErrorCode): 134 status = e.status 135 else: 136 failure.printTraceback() 137 138 response = soap.build_soap_error(status) 139 return self._build_response(request, response, response_obj, 140 status=status)
141
142 - def _build_response(self, request, response, response_object, status=200):
143 """ Builds a response for a call. 144 """ 145 if status == 200: 146 response_object.status = 200 147 else: 148 response_object.status = 500 149 150 if self.encoding is not None: 151 mime_type = 'text/xml; charset="%s"' % self.encoding 152 else: 153 mime_type = "text/xml" 154 response_object.headers["Content-type"] = mime_type 155 response_object.headers["Content-length"] = str(len(response)) 156 response_object.headers["EXT"] = '' 157 response_object.body = response 158 return response
159
160 - def _method_not_found(self, request, response_obj, method_name):
161 """ Treats the method not found error. 162 """ 163 response = soap.build_soap_error(401) 164 return self._build_response(request, response, response_obj, 165 status=401)
166 167
168 -class Service(BaseService):
169
170 - def __init__(self, id, serv_type, url_base='', 171 scpd_xml_filepath='', presentation_controller=None):
172 BaseService.__init__(self, id, serv_type, url_base) 173 174 self.control_controller = ServiceController(self, self.service_type) 175 self.eventSub_controller = None 176 self.presentation_controller = presentation_controller 177 178 if not scpd_xml_filepath: 179 self._generate_xml() 180 self._create_xml = True 181 else: 182 self._xml_filepath = scpd_xml_filepath 183 fd = open(self._xml_filepath, 'r') 184 if not ServiceBuilder(self, fd).build(): 185 raise InvalidService('Error building the service %s' % id) 186 fd.close() 187 self._create_xml = False
188
189 - def publish(self, webserver):
190 log.info('Publishing service %s' % self.id) 191 192 if not len(self.get_variables()): 193 raise InvalidService('The service must have one '\ 194 'or more state variables') 195 196 res = webserver.CustomResource(self.id) 197 if self._create_xml: 198 ServiceXMLBuilder(self).generate_to_file(self._xml_filepath) 199 f = webserver.StaticFile('scpd.xml', self._xml_filepath) 200 res.add_static_file(f) 201 202 for k in ['control', 'eventSub', 'presentation']: 203 controller = getattr(self, '%s_controller' % k) 204 if controller: 205 res.add_resource(controller) 206 207 webserver.add_resource(res)
208
209 - def add_state_variable(self, state_variable):
210 if not self.eventSub_controller and state_variable.send_events: 211 self.eventSub_controller = EventController(self) 212 self._state_variables[state_variable.name] = state_variable
213
214 - def set_state_variable(self, name, value):
215 state_variable = self._state_variables[name] 216 state_variable.update(value)
217
218 - def add_action(self, action):
219 action.service = self 220 self._actions[action.name] = action
221
222 - def start(self, *args, **kwargs):
223 pass
224
225 - def _generate_xml(self):
226 self.xml_filename = '%s-scpd.xml' % self.id 227 self.xml_filename = self.xml_filename.replace(' ', '') 228 self._xml_filepath = path.join(config.manager.brisa_home, 'tmp_xml') 229 if not path.exists(self._xml_filepath): 230 mkdir(self._xml_filepath) 231 self._xml_filepath = path.join(self._xml_filepath, self.xml_filename)
232