<commit_before>

"""control point classes"""

__author__ = '<NAME> <EMAIL>'
__copyright__ = 'Copyright (c) 2014 SFR (http://www.sfr.com)'
__license__ = 'GNU LESSER GENERAL PUBLIC LICENSE Version 2.1'

import utils 

try:
    from xml.etree import cElementTree as ElementTree
except:
    from xml.etree import ElementTree
import logging

SQNS = utils.Namespace('http://schemas.xmlsoap.org/soap/envelope/', 'soap')
DNS = utils.Namespace('urn:schemas-upnp-org:device-1-0', 'device')
SNS = utils.Namespace('urn:schemas-upnp-org:service-1-0', 'service')
CNS = utils.Namespace('urn:schemas-upnp-org:control-1-0', 'control')
ENS = utils.Namespace('urn:schemas-upnp-org:event-1-0', 'event')
SES = "http://schemas.xmlsoap.org/soap/encoding/"

class UPnPObject(object):

    _DESCRIPTIONS = dict()

    def __init__(self, upnpy, location, parent=None, handler=None):

        #temporary logger replaced after object description
        self._logger = logging.getLogger(self.__class__.__name__)

        self._upnpy = upnpy
        self._handler = handler
        self._location = location
        self._parent = parent
        
    def _byebye(self):
        self._parents[0]._clean()

    def _request(self, url, method, callback, *args, **kwargs):

        return self._upnpy.http_request(url, method, callback, *args, **kwargs)

    def _do_describe(self, location, cb):
        if location in self._DESCRIPTIONS:
            return cb(type('Response', (object,), dict(
                        body=self._DESCRIPTIONS[location],
                        code='200')))

        self._request(location, 'GET', lambda resp: self._do_described(location, resp, cb))

    def _do_described(self, location, response, cb):

        desc = response.body

        if response.code == '200' and desc:
            self._DESCRIPTIONS[location] = desc

        cb(response)

    @property
    def _parents(self):
        ret = [self]
        while ret[0]._parent:
            ret.insert(0, ret[0]._parent)
        return ret

    def __del__(self):
        self._parents[0]._clean()

class Service(UPnPObject):

    _ATTRS = ['serviceType', 'serviceId', 'SCPDURL', 'controlURL', 'eventSubURL']
    EXPIRY = 300

    def __init__(self, upnpy, location, parent=None, handler=None, desc=None):
        super(Service, self).__init__(upnpy, location, parent, handler)

        self._subscription = None
        self._parse_short(desc)
        self._statevalues = {}

    def _parse_short(self, desc):

        for tag in self._ATTRS:
            n = desc.find(DNS(tag))
            setattr(self, tag, n.text if n is not None else None)

        st = self.serviceType.split(':')
        if len(st) > 3:
            st = st[3]
        elif len(st) > 1:
            st = st[1]
        else:
            st = ":".join(st)
        self._shortType = st

        self._logger = logging.getLogger('service.%s:%s' % (self._parent.friendlyName, self._shortType))

        if self.SCPDURL:
            self._describe()

    def _describe(self):
        self._do_describe(self._absurl(self.SCPDURL), self._parse)
            
    def _parse(self, response):

        desc = None
        if response.code != '200' or not response.body:
            self._logger.error('cannot access service description : %s %s at %s', response.code, response.status, self._absurl(self.SCPDURL))
        else:
            try:
                desc = ElementTree.ElementTree(ElementTree.fromstring(response.body))
            except Exception, e:
                self._logger.error('cannot parse service description : %s %r', str(e), response.body)

        if isinstance(desc, ElementTree.ElementTree):
            sl = desc.find(SNS('serviceStateTable'))
            if sl is not None:
                for e in sl.findall(SNS('stateVariable')):
                    a = StateVariable(self, e)
                    setattr(self, a.identifier, a)
            al = desc.find(SNS('actionList'))
            if al is not None:
                for e in al.findall(SNS('action')):
                    a = Action(self, e)                    
                    setattr(self, a.identifier, a.fct)

        if self._handler:
            self._handler(self)

        if self.eventSubURL:
            self._subscribe()

    def _action(self, action, _cb, args):
        
        ret = self._request(self._absurl(self.controlURL), 'POST',
                            (lambda response:self._result(action, response, _cb)) if _cb else None,
                            headers = {'Content-Type': 'text/xml; charset="utf-8"',
                                       'SOAPAction':'"%s#%s"' % (self.serviceType, action)},
                            body=self._soapQuery(action, args))
        if not _cb:
            return self._action_result(action, ret, _cb)

    def _action_result(self, action, response, cb):
        self._logger.debug(response.body)
        ret = self._parseSoapResponse(action, response)
        if cb:
            cb(**ret)
        else:
            return ret

    def _subscribe(self):

        if not self._subscription:
            self._subscription = Subscription(self)
        else:
            self._subscription.renew()
        
    def _unsubscribe(self):
        if self._subscription:
            self._subscription.unsubscribe()

    def _notify(self, request):
        if request.method != 'NOTIFY':
            self._logger.warning('invalid method on notification path : %s', request.method)

        if request.headers.get('SID', '') != getattr(self._subscription, 'sid', None):
            return (412,)
        
        try:
            body = request.body
            body = ElementTree.fromstring(body[body.index('<'):body.rindex('>')+1])
        except ElementTree.ParseError, e:
            self._logger.exception('Cannot parse %r', request.body)
            return (500,)

        stnse = self._stns("")

        for p in body.findall(ENS('property')):
            t = p[0]
            name = t.tag
            if name.startswith(stnse):
                name = name[len(stnse):]
            value = t.text
            if getattr(self, name, None) != value:
                self._set_state_value(name, value)            

        return (200,)

    def _set_state_value(self, name, value):
        setattr(self, name, value)

    def _soapQuery(self, action, args):

        env = ElementTree.Element(SQNS('Envelope'), {SQNS('encodingStyle'):SES})
        act = ElementTree.SubElement(ElementTree.SubElement(env,SQNS('Body')), self._stns(action))
        
        for k, v in args.iteritems():
            e = ElementTree.SubElement(act, self._stns(k))
            if v is not None:
                e.text=str(v)
            
        return utils.tostring(env, encoding='utf-8', default_namespace=SQNS.ns, xml_declaration=True)

    def _parseSoapResponse(self, action, response):

        ret = {}

        if response.body is None:
            raise ActionError('HTTP body not found (http : %s %s)', response.code, response.body)        

        body = ElementTree.fromstring(response.body).find(SQNS('Body'))
        if body is None:
            raise ActionError('Soap body not found')

        action_response = body.find(self._stns(action+'Response'))
        if action_response is None and body.find(SQNS('Fault')) is None and len(list(body))>0:
            action_response = list(body)[0]
            self._logger.warning("spurious response node tag '%s'", action_response.tag)       

        if action_response is None:
            
            fault = body.find(SQNS('Fault'))
            if fault is None:              
                raise ActionError('No response found')
<commit_msg>remove default namespace for action call , interoperability issues<commit_after>

"""control point classes"""

__author__ = '<NAME> <EMAIL>'
__copyright__ = 'Copyright (c) 2014 SFR (http://www.<EMAIL>)'
__license__ = 'GNU LESSER GENERAL PUBLIC LICENSE Version 2.1'

import utils 

try:
    from xml.etree import cElementTree as ElementTree
except:
    from xml.etree import ElementTree
import logging

SQNS = utils.Namespace('http://schemas.xmlsoap.org/soap/envelope/', 'soap')
DNS = utils.Namespace('urn:schemas-upnp-org:device-1-0', 'device')
SNS = utils.Namespace('urn:schemas-upnp-org:service-1-0', 'service')
CNS = utils.Namespace('urn:schemas-upnp-org:control-1-0', 'control')
ENS = utils.Namespace('urn:schemas-upnp-org:event-1-0', 'event')
SES = "http://schemas.xmlsoap.org/soap/encoding/"

class UPnPObject(object):

    _DESCRIPTIONS = dict()

    def __init__(self, upnpy, location, parent=None, handler=None):

        #temporary logger replaced after object description
        self._logger = logging.getLogger(self.__class__.__name__)

        self._upnpy = upnpy
        self._handler = handler
        self._location = location
        self._parent = parent
        
    def _byebye(self):
        self._parents[0]._clean()

    def _request(self, url, method, callback, *args, **kwargs):

        return self._upnpy.http_request(url, method, callback, *args, **kwargs)

    def _do_describe(self, location, cb):
        if location in self._DESCRIPTIONS:
            return cb(type('Response', (object,), dict(
                        body=self._DESCRIPTIONS[location],
                        code='200')))

        self._request(location, 'GET', lambda resp: self._do_described(location, resp, cb))

    def _do_described(self, location, response, cb):

        desc = response.body

        if response.code == '200' and desc:
            self._DESCRIPTIONS[location] = desc

        cb(response)

    @property
    def _parents(self):
        ret = [self]
        while ret[0]._parent:
            ret.insert(0, ret[0]._parent)
        return ret

    def __del__(self):
        self._parents[0]._clean()

class Service(UPnPObject):

    _ATTRS = ['serviceType', 'serviceId', 'SCPDURL', 'controlURL', 'eventSubURL']
    EXPIRY = 300

    def __init__(self, upnpy, location, parent=None, handler=None, desc=None):
        super(Service, self).__init__(upnpy, location, parent, handler)

        self._subscription = None
        self._parse_short(desc)
        self._statevalues = {}

    def _parse_short(self, desc):

        for tag in self._ATTRS:
            n = desc.find(DNS(tag))
            setattr(self, tag, n.text if n is not None else None)

        st = self.serviceType.split(':')
        if len(st) > 3:
            st = st[3]
        elif len(st) > 1:
            st = st[1]
        else:
            st = ":".join(st)
        self._shortType = st

        self._logger = logging.getLogger('service.%s:%s' % (self._parent.friendlyName, self._shortType))

        if self.SCPDURL:
            self._describe()

    def _describe(self):
        self._do_describe(self._absurl(self.SCPDURL), self._parse)
            
    def _parse(self, response):

        desc = None
        if response.code != '200' or not response.body:
            self._logger.error('cannot access service description : %s %s at %s', response.code, response.status, self._absurl(self.SCPDURL))
        else:
            try:
                desc = ElementTree.ElementTree(ElementTree.fromstring(response.body))
            except Exception, e:
                self._logger.error('cannot parse service description : %s %r', str(e), response.body)

        if isinstance(desc, ElementTree.ElementTree):
            sl = desc.find(SNS('serviceStateTable'))
            if sl is not None:
                for e in sl.findall(SNS('stateVariable')):
                    a = StateVariable(self, e)
                    setattr(self, a.identifier, a)
            al = desc.find(SNS('actionList'))
            if al is not None:
                for e in al.findall(SNS('action')):
                    a = Action(self, e)                    
                    setattr(self, a.identifier, a.fct)

        if self._handler:
            self._handler(self)

        if self.eventSubURL:
            self._subscribe()

    def _action(self, action, _cb, args):
        
        ret = self._request(self._absurl(self.controlURL), 'POST',
                            (lambda response:self._result(action, response, _cb)) if _cb else None,
                            headers = {'Content-Type': 'text/xml; charset="utf-8"',
                                       'SOAPAction':'"%s#%s"' % (self.serviceType, action)},
                            body=self._soapQuery(action, args))
        if not _cb:
            return self._action_result(action, ret, _cb)

    def _action_result(self, action, response, cb):
        self._logger.debug(response.body)
        ret = self._parseSoapResponse(action, response)
        if cb:
            cb(**ret)
        else:
            return ret

    def _subscribe(self):

        if not self._subscription:
            self._subscription = Subscription(self)
        else:
            self._subscription.renew()
        
    def _unsubscribe(self):
        if self._subscription:
            self._subscription.unsubscribe()

    def _notify(self, request):
        if request.method != 'NOTIFY':
            self._logger.warning('invalid method on notification path : %s', request.method)

        if request.headers.get('SID', '') != getattr(self._subscription, 'sid', None):
            return (412,)
        
        try:
            body = request.body
            body = ElementTree.fromstring(body[body.index('<'):body.rindex('>')+1])
        except ElementTree.ParseError, e:
            self._logger.exception('Cannot parse %r', request.body)
            return (500,)

        stnse = self._stns("")

        for p in body.findall(ENS('property')):
            t = p[0]
            name = t.tag
            if name.startswith(stnse):
                name = name[len(stnse):]
            value = t.text
            if getattr(self, name, None) != value:
                self._set_state_value(name, value)            

        return (200,)

    def _set_state_value(self, name, value):
        setattr(self, name, value)

    def _soapQuery(self, action, args):

        env = ElementTree.Element(SQNS('Envelope'), {SQNS('encodingStyle'):SES})
        act = ElementTree.SubElement(ElementTree.SubElement(env,SQNS('Body')), self._stns(action))
        
        for k, v in args.iteritems():
            e = ElementTree.SubElement(act, self._stns(k))
            if v is not None:
                e.text=str(v)
            
        return utils.tostring(env, encoding='utf-8', xml_declaration=True)
        #default_namespace=SQNS.ns)
        #does not seem to behave correctly with some xml library (libupnp, ..)

    def _parseSoapResponse(self, action, response):

        ret = {}

        if response.body is None:
            raise ActionError('HTTP body not found (http : %s %s)', response.code, response.body)        

        body = ElementTree.fromstring(response.body).find(SQNS('Body'))
        if body is None:
            raise ActionError('Soap body not found')

        action_response = body.find(self._stns(action+'Response'))
        if action_response is None and body.find(SQNS('Fault')) is None and len(list(body))>0:
            action_response = list(body)[0]
            self._logger.warning("spurious response node tag '%s'", action_response.tag)       

        if action_response is None:
            
            fault = body.find(SQNS('Fault'))
            if fault is None:              
                raise ActionError('No response found')

{'fraction_non_alphanumeric': 0.122376493380691, 'fraction_numerical': 0.006457862447529867, 'mean_word_length': 2.104651162790698, 'pattern_counts': {'":': 2, '<': 10, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 13}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3050384', 'n_tokens_mistral': 4665, 'n_tokens_neox': 4450, 'n_words': 1056}