diff options
Diffstat (limited to 'indra/lib')
| -rw-r--r-- | indra/lib/python/indra/__init__.py | 12 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/__init__.py | 7 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/config.py | 46 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/llsd.py | 808 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/lluuid.py | 265 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/__init__.py | 7 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/compatibility.py | 103 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/httputil.py | 94 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/llmessage.py | 353 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/llsdhttp.py | 257 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/russ.py | 123 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/servicebuilder.py | 52 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/tokenstream.py | 134 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/xml_rpc.py | 253 | ||||
| -rw-r--r-- | indra/lib/python/indra/util/__init__.py | 7 | ||||
| -rw-r--r-- | indra/lib/python/indra/util/helpformatter.py | 32 | ||||
| -rw-r--r-- | indra/lib/python/indra/util/llmanifest.py | 562 | 
17 files changed, 3110 insertions, 5 deletions
| diff --git a/indra/lib/python/indra/__init__.py b/indra/lib/python/indra/__init__.py index 7db53666e8..bbfdbaf98d 100644 --- a/indra/lib/python/indra/__init__.py +++ b/indra/lib/python/indra/__init__.py @@ -1,5 +1,7 @@ -# @file __init__.py -# @brief Initialization file for the indra module. -#  -# Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. -# $License$ +"""\ +@file __init__.py +@brief Initialization file for the indra module. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" diff --git a/indra/lib/python/indra/base/__init__.py b/indra/lib/python/indra/base/__init__.py new file mode 100644 index 0000000000..e615ab6870 --- /dev/null +++ b/indra/lib/python/indra/base/__init__.py @@ -0,0 +1,7 @@ +"""\ +@file __init__.py +@brief Initialization file for the indra.base module. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" diff --git a/indra/lib/python/indra/base/config.py b/indra/lib/python/indra/base/config.py new file mode 100644 index 0000000000..d76bd7edd3 --- /dev/null +++ b/indra/lib/python/indra/base/config.py @@ -0,0 +1,46 @@ +"""\ +@file config.py +@brief Utility module for parsing and accessing the indra.xml config file. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" + +from os.path import dirname, join, realpath +import types +from indra.base import llsd + +_g_config_dict = None + +def load(indra_xml_file=None): +    global _g_config_dict +    if _g_config_dict == None: +        if indra_xml_file is None: +            ## going from: +            ## "/opt/linden/indra/lib/python/indra/base/config.py" +            ## to: +            ## "/opt/linden/etc/indra.xml" +            indra_xml_file = realpath( +                dirname(realpath(__file__)) + "../../../../../../etc/indra.xml") +        config_file = file(indra_xml_file) +        _g_config_dict = llsd.LLSD().parse(config_file.read()) +        config_file.close() +        #print "loaded config from",indra_xml_file,"into",_g_config_dict + +def update(xml_file): +    """Load an XML file and apply its map as overrides or additions +    to the existing config.  The dataserver does this with indra.xml +    and dataserver.xml.""" +    global _g_config_dict +    if _g_config_dict == None: +        raise Exception("no config loaded before config.update()") +    config_file = file(xml_file) +    overrides = llsd.LLSD().parse(config_file.read()) +    config_file.close() +    _g_config_dict.update(overrides) + +def get(key): +    global _g_config_dict +    if _g_config_dict == None: +        load() +    return _g_config_dict.get(key) diff --git a/indra/lib/python/indra/base/llsd.py b/indra/lib/python/indra/base/llsd.py new file mode 100644 index 0000000000..1af07e5885 --- /dev/null +++ b/indra/lib/python/indra/base/llsd.py @@ -0,0 +1,808 @@ +"""\ +@file llsd.py +@brief Types as well as parsing and formatting functions for handling LLSD. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" + +import datetime +import base64 +import struct +import time +import types +import re + +#from cElementTree import fromstring ## This does not work under Windows +try: +    ## This is the old name of elementtree, for use with 2.3 +    from elementtree.ElementTree import fromstring +except ImportError: +    ## This is the name of elementtree under python 2.5 +    from xml.etree.ElementTree import fromstring + +from indra.base import lluuid + +int_regex = re.compile("[-+]?\d+") +real_regex = re.compile("[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") +alpha_regex = re.compile("[a-zA-Z]+") +date_regex = re.compile("(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<second_float>\.\d{2})?Z") +#date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + +class LLSDParseError(Exception): +    pass + +class LLSDSerializationError(Exception): +    pass + + +class binary(str): +    pass + +class uri(str): +    pass + + +BOOL_TRUE = ('1', '1.0', 'true') +BOOL_FALSE = ('0', '0.0', 'false', '') + + +def bool_to_python(node): +    val = node.text or '' +    if val in BOOL_TRUE: +        return True +    else: +        return False + +def int_to_python(node): +    val = node.text or '' +    if not val.strip(): +        return 0 +    return int(val) + +def real_to_python(node): +    val = node.text or '' +    if not val.strip(): +        return 0.0 +    return float(val) + +def uuid_to_python(node): +    return lluuid.UUID(node.text) + +def str_to_python(node): +    return unicode(node.text or '').encode('utf8', 'replace') + +def bin_to_python(node): +    return binary(base64.decodestring(node.text or '')) + +def date_to_python(node): +    val = node.text or '' +    if not val: +        val = "1970-01-01T00:00:00Z" +    return datetime.datetime( +        *time.strptime(val, '%Y-%m-%dT%H:%M:%SZ')[:6]) + +def uri_to_python(node): +    val = node.text or '' +    if not val: +        return None +    return uri(val) + +def map_to_python(node): +    result = {} +    for index in range(len(node))[::2]: +        result[node[index].text] = to_python(node[index+1]) +    return result + +def array_to_python(node): +    return [to_python(child) for child in node] + + +NODE_HANDLERS = dict( +    undef=lambda x: None, +    boolean=bool_to_python, +    integer=int_to_python, +    real=real_to_python, +    uuid=uuid_to_python, +    string=str_to_python, +    binary=bin_to_python, +    date=date_to_python, +    uri=uri_to_python, +    map=map_to_python, +    array=array_to_python, +    ) + +def to_python(node): +    return NODE_HANDLERS[node.tag](node) + +class Nothing(object): +    pass + + +class LLSDXMLFormatter(object): +    def __init__(self): +        self.type_map = { +            type(None) : self.UNDEF, +            bool : self.BOOLEAN, +            int : self.INTEGER, +            long : self.INTEGER, +            float : self.REAL, +            lluuid.UUID : self.UUID, +            binary : self.BINARY, +            str : self.STRING, +            unicode : self.STRING, +            uri : self.URI, +            datetime.datetime : self.DATE, +            list : self.ARRAY, +            tuple : self.ARRAY, +            types.GeneratorType : self.ARRAY, +            dict : self.MAP +        } + +    def elt(self, name, contents=None): +        if(contents is None or contents is ''): +            return "<%s />" % (name,) +        else: +            return "<%s>%s</%s>" % (name, contents, name) + +    def xml_esc(self, v): +        return v.replace('&', '&').replace('<', '<').replace('>', '>') + +    def UNDEF(self, v): +        return self.elt('undef') +    def BOOLEAN(self, v): +        if v: +            return self.elt('boolean', 'true') +        else: +            return self.elt('boolean', 'false') +    def INTEGER(self, v): +        return self.elt('integer', v) +    def REAL(self, v): +        return self.elt('real', v) +    def UUID(self, v): +        if(v.isNull()): +            return self.elt('uuid') +        else: +            return self.elt('uuid', v) +    def BINARY(self, v): +        return self.elt('binary', base64.encodestring(v)) +    def STRING(self, v): +        return self.elt('string', self.xml_esc(v)) +    def URI(self, v): +        return self.elt('uri', self.xml_esc(str(v))) +    def DATE(self, v): +        return self.elt('date', v.strftime('%Y-%m-%dT%H:%M:%SZ')) +    def ARRAY(self, v): +        return self.elt('array', ''.join([self.generate(item) for item in v])) +    def MAP(self, v): +        return self.elt( +            'map', +            ''.join(["%s%s" % (self.elt('key', key), self.generate(value)) +             for key, value in v.items()])) + +    def generate(self, something): +        t = type(something) +        if self.type_map.has_key(t): +            return self.type_map[t](something) +        else: +            raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( +                t, something)) + +    def format(self, something): +        return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) + +def format_xml(something): +    return LLSDXMLFormatter().format(something) + +class LLSDNotationFormatter(object): +    def __init__(self): +        self.type_map = { +            type(None) : self.UNDEF, +            bool : self.BOOLEAN, +            int : self.INTEGER, +            long : self.INTEGER, +            float : self.REAL, +            lluuid.UUID : self.UUID, +            binary : self.BINARY, +            str : self.STRING, +            unicode : self.STRING, +            uri : self.URI, +            datetime.datetime : self.DATE, +            list : self.ARRAY, +            tuple : self.ARRAY, +            types.GeneratorType : self.ARRAY, +            dict : self.MAP +        } + +    def UNDEF(self, v): +        return '!' +    def BOOLEAN(self, v): +        if v: +            return 'true' +        else: +            return 'false' +    def INTEGER(self, v): +        return "i%s" % v +    def REAL(self, v): +        return "r%s" % v +    def UUID(self, v): +        return "u%s" % v +    def BINARY(self, v): +        raise LLSDSerializationError("binary notation not yet supported") +    def STRING(self, v): +        return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") +    def URI(self, v): +        return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') +    def DATE(self, v): +        second_str = "" +        if v.microsecond > 0: +            seconds = v.second + float(v.microsecond) / 1000000 +            second_str = "%05.2f" % seconds +        else: +            second_str = "%d" % v.second +        return 'd"%s%sZ"' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str) +    def ARRAY(self, v): +        return "[%s]" % ','.join([self.generate(item) for item in v]) +    def MAP(self, v): +        return "{%s}" % ','.join(["'%s':%s" % (key.replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) +             for key, value in v.items()]) + +    def generate(self, something): +        t = type(something) +        if self.type_map.has_key(t): +            return self.type_map[t](something) +        else: +            raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( +                t, something)) + +    def format(self, something): +        return self.generate(something) + +def format_notation(something): +    return LLSDNotationFormatter().format(something) + +def _hex_as_nybble(hex): +    if (hex >= '0') and (hex <= '9'): +        return ord(hex) - ord('0') +    elif (hex >= 'a') and (hex <='f'): +        return 10 + ord(hex) - ord('a') +    elif (hex >= 'A') and (hex <='F'): +        return 10 + ord(hex) - ord('A'); + +class LLSDBinaryParser(object): +    def __init__(self): +        pass + +    def parse(self, buffer, ignore_binary = False): +        """ +        This is the basic public interface for parsing. + +        @param buffer the binary data to parse in an indexable sequence. +        @param ignore_binary parser throws away data in llsd binary nodes. +        @return returns a python object. +        """ +        self._buffer = buffer +        self._index = 0 +        self._keep_binary = not ignore_binary +        return self._parse() + +    def _parse(self): +        cc = self._buffer[self._index] +        self._index += 1 +        if cc == '{': +            return self._parse_map() +        elif cc == '[': +            return self._parse_array() +        elif cc == '!': +            return None +        elif cc == '0': +            return False +        elif cc == '1': +            return True +        elif cc == 'i': +            # 'i' = integer +            idx = self._index +            self._index += 4 +            return struct.unpack("!i", self._buffer[idx:idx+4])[0] +        elif cc == ('r'): +            # 'r' = real number +            idx = self._index +            self._index += 8 +            return struct.unpack("!d", self._buffer[idx:idx+8])[0] +        elif cc == 'u': +            # 'u' = uuid +            idx = self._index +            self._index += 16 +            return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) +        elif cc == 's': +            # 's' = string +            return self._parse_string() +        elif cc in ("'", '"'): +            # delimited/escaped string +            return self._parse_string_delim(cc) +        elif cc == 'l': +            # 'l' = uri +            return uri(self._parse_string()) +        elif cc == ('d'): +            # 'd' = date in seconds since epoch +            idx = self._index +            self._index += 8 +            seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] +            return datetime.datetime.fromtimestamp(seconds) +        elif cc == 'b': +            binary = self._parse_string() +            if self._keep_binary: +                return binary +            # *NOTE: maybe have a binary placeholder which has the +            # length. +            return None +        else: +            raise LLSDParseError("invalid binary token at byte %d: %d" % ( +                self._index - 1, ord(cc))) + +    def _parse_map(self): +        rv = {} +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        count = 0 +        cc = self._buffer[self._index] +        self._index += 1 +        key = '' +        while (cc != '}') and (count < size): +            if cc == 'k': +                key = self._parse_string() +            elif cc in ("'", '"'): +                key = self._parse_string_delim(cc) +            else: +                raise LLSDParseError("invalid map key at byte %d." % ( +                    self._index - 1,)) +            value = self._parse() +            #print "kv:",key,value +            rv[key] = value +            count += 1 +            cc = self._buffer[self._index] +            self._index += 1 +        if cc != '}': +            raise LLSDParseError("invalid map close token at byte %d." % ( +                self._index,)) +        return rv + +    def _parse_array(self): +        rv = [] +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        count = 0 +        cc = self._buffer[self._index] +        while (cc != ']') and (count < size): +            rv.append(self._parse()) +            count += 1 +            cc = self._buffer[self._index] +        if cc != ']': +            raise LLSDParseError("invalid array close token at byte %d." % ( +                self._index,)) +        self._index += 1 +        return rv + +    def _parse_string(self): +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        rv = self._buffer[self._index:self._index+size] +        self._index += size +        return rv + +    def _parse_string_delim(self, delim): +        list = [] +        found_escape = False +        found_hex = False +        found_digit = False +        byte = 0 +        while True: +            cc = self._buffer[self._index] +            self._index += 1 +            if found_escape: +                if found_hex: +                    if found_digit: +                        found_escape = False +                        found_hex = False +                        found_digit = False +                        byte <<= 4 +                        byte |= _hex_as_nybble(cc) +                        list.append(chr(byte)) +                        byte = 0 +                    else: +                        found_digit = True +                        byte = _hex_as_nybble(cc) +                elif cc == 'x': +                    found_hex = True +                else: +                    if cc == 'a': +                        list.append('\a') +                    elif cc == 'b': +                        list.append('\b') +                    elif cc == 'f': +                        list.append('\f') +                    elif cc == 'n': +                        list.append('\n') +                    elif cc == 'r': +                        list.append('\r') +                    elif cc == 't': +                        list.append('\t') +                    elif cc == 'v': +                        list.append('\v') +                    else: +                        list.append(cc) +                    found_escape = False +            elif cc == '\\': +                found_escape = True +            elif cc == delim: +                break +            else: +                list.append(cc) +        return ''.join(list) + +class LLSDNotationParser(object): +    """ Parse LLSD notation: +    map: { string:object, string:object } +    array: [ object, object, object ] +    undef: ! +    boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE +    integer: i#### +    real: r#### +    uuid: u#### +    string: "g'day" | 'have a "nice" day' | s(size)"raw data" +    uri: l"escaped" +    date: d"YYYY-MM-DDTHH:MM:SS.FFZ" +    binary: b##"ff3120ab1" | b(size)"raw data" """ +    def __init__(self): +        pass + +    def parse(self, buffer, ignore_binary = False): +        """ +        This is the basic public interface for parsing. + +        @param buffer the notation string to parse. +        @param ignore_binary parser throws away data in llsd binary nodes. +        @return returns a python object. +        """ +        if buffer == "": +            return False + +        self._buffer = buffer +        self._index = 0 +        return self._parse() + +    def _parse(self): +        cc = self._buffer[self._index] +        self._index += 1 +        if cc == '{': +            return self._parse_map() +        elif cc == '[': +            return self._parse_array() +        elif cc == '!': +            return None +        elif cc == '0': +            return False +        elif cc == '1': +            return True +        elif cc in ('F', 'f'): +            self._skip_alpha() +            return False +        elif cc in ('T', 't'): +            self._skip_alpha() +            return True +        elif cc == 'i': +            # 'i' = integer +            return self._parse_integer() +        elif cc == ('r'): +            # 'r' = real number +            return self._parse_real() +        elif cc == 'u': +            # 'u' = uuid +            return self._parse_uuid() +        elif cc in ("'", '"', 's'): +            return self._parse_string(cc) +        elif cc == 'l': +            # 'l' = uri +            delim = self._buffer[self._index] +            self._index += 1 +            val = uri(self._parse_string(delim)) +            if len(val) == 0: +                return None +            return val +        elif cc == ('d'): +            # 'd' = date in seconds since epoch +            return self._parse_date() +        elif cc == 'b': +            raise LLSDParseError("binary notation not yet supported") +        else: +            print cc +            raise LLSDParseError("invalid token at index %d: %d" % ( +                self._index - 1, ord(cc))) + +    def _parse_map(self): +        """ map: { string:object, string:object } """ +        rv = {} +        cc = self._buffer[self._index] +        self._index += 1 +        key = '' +        found_key = False +        while (cc != '}'): +            if not found_key: +                if cc in ("'", '"', 's'): +                    key = self._parse_string(cc) +                    found_key = True +                    #print "key:",key +                elif cc.isspace() or cc == ',': +                    cc = self._buffer[self._index] +                    self._index += 1 +                else: +                    raise LLSDParseError("invalid map key at byte %d." % ( +                                        self._index - 1,)) +            else: +                if cc.isspace() or cc == ':': +                    #print "skipping whitespace '%s'" % cc +                    cc = self._buffer[self._index] +                    self._index += 1 +                    continue +                self._index += 1 +                value = self._parse() +                #print "kv:",key,value +                rv[key] = value +                found_key = False +                cc = self._buffer[self._index] +                self._index += 1 +                #if cc == '}': +                #    break +                #cc = self._buffer[self._index] +                #self._index += 1 + +        return rv + +    def _parse_array(self): +        """ array: [ object, object, object ] """ +        rv = [] +        cc = self._buffer[self._index] +        while (cc != ']'): +            if cc.isspace() or cc == ',': +                self._index += 1 +                cc = self._buffer[self._index] +                continue +            rv.append(self._parse()) +            cc = self._buffer[self._index] + +        if cc != ']': +            raise LLSDParseError("invalid array close token at index %d." % ( +                self._index,)) +        self._index += 1 +        return rv + +    def _parse_uuid(self): +        match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid uuid token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return lluuid.UUID(self._buffer[start:end]) + +    def _skip_alpha(self): +        match = re.match(alpha_regex, self._buffer[self._index:]) +        if match: +            self._index += match.end() +             +    def _parse_date(self): +        delim = self._buffer[self._index] +        self._index += 1 +        datestr = self._parse_string(delim) + +        if datestr == "": +            return datetime.datetime(1970, 1, 1) +             +        match = re.match(date_regex, datestr) +        if not match: +            raise LLSDParseError("invalid date string '%s'." % datestr) + +        year = int(match.group('year')) +        month = int(match.group('month')) +        day = int(match.group('day')) +        hour = int(match.group('hour')) +        minute = int(match.group('minute')) +        second = int(match.group('second')) +        seconds_float = match.group('second_float') +        microsecond = 0 +        if seconds_float: +            microsecond = int(seconds_float[1:]) * 10000 +        return datetime.datetime(year, month, day, hour, minute, second, microsecond) + +    def _parse_real(self): +        match = re.match(real_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid real token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return float( self._buffer[start:end] ) + +    def _parse_integer(self): +        match = re.match(int_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid integer token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return int( self._buffer[start:end] ) + +    def _parse_string(self, delim): +        """ string: "g'day" | 'have a "nice" day' | s(size)"raw data" """ +        rv = "" + +        if delim in ("'", '"'): +            rv = self._parse_string_delim(delim) +        elif delim == 's': +            rv = self._parse_string_raw() +        else: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        return rv + + +    def _parse_string_delim(self, delim): +        """ string: "g'day" | 'have a "nice" day' """ +        list = [] +        found_escape = False +        found_hex = False +        found_digit = False +        byte = 0 +        while True: +            cc = self._buffer[self._index] +            self._index += 1 +            if found_escape: +                if found_hex: +                    if found_digit: +                        found_escape = False +                        found_hex = False +                        found_digit = False +                        byte <<= 4 +                        byte |= _hex_as_nybble(cc) +                        list.append(chr(byte)) +                        byte = 0 +                    else: +                        found_digit = True +                        byte = _hex_as_nybble(cc) +                elif cc == 'x': +                    found_hex = True +                else: +                    if cc == 'a': +                        list.append('\a') +                    elif cc == 'b': +                        list.append('\b') +                    elif cc == 'f': +                        list.append('\f') +                    elif cc == 'n': +                        list.append('\n') +                    elif cc == 'r': +                        list.append('\r') +                    elif cc == 't': +                        list.append('\t') +                    elif cc == 'v': +                        list.append('\v') +                    else: +                        list.append(cc) +                    found_escape = False +            elif cc == '\\': +                found_escape = True +            elif cc == delim: +                break +            else: +                list.append(cc) +        return ''.join(list) + +    def _parse_string_raw(self): +        """ string: s(size)"raw data" """ +        # Read the (size) portion. +        cc = self._buffer[self._index] +        self._index += 1 +        if cc != '(': +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        rparen = self._buffer.find(')', self._index) +        if rparen == -1: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        size = int(self._buffer[self._index:rparen]) + +        self._index = rparen + 1 +        delim = self._buffer[self._index] +        self._index += 1 +        if delim not in ("'", '"'): +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        rv = self._buffer[self._index:(self._index + size)] +        self._index += size +        cc = self._buffer[self._index] +        self._index += 1 +        if cc != delim: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        return rv +         +def format_binary(something): +    return '<?llsd/binary?>\n' + _format_binary_recurse(something) + +def _format_binary_recurse(something): +    if something is None: +        return '!' +    elif isinstance(something, bool): +        if something: +            return '1' +        else: +            return '0' +    elif isinstance(something, (int, long)): +        return 'i' + struct.pack('!i', something) +    elif isinstance(something, float): +        return 'r' + struct.pack('!d', something) +    elif isinstance(something, lluuid.UUID): +        return 'u' + something._bits +    elif isinstance(something, binary): +        return 'b' + struct.pack('!i', len(something)) + something +    elif isinstance(something, (str, unicode)): +        return 's' + struct.pack('!i', len(something)) + something +    elif isinstance(something, uri): +        return 'l' + struct.pack('!i', len(something)) + something +    elif isinstance(something, datetime.datetime): +        seconds_since_epoch = time.mktime(something.timetuple()) +        return 'd' + struct.pack('!d', seconds_since_epoch) +    elif isinstance(something, (list, tuple)): +        array_builder = [] +        array_builder.append('[' + struct.pack('!i', len(something))) +        for item in something: +            array_builder.append(_format_binary_recurse(item)) +        array_builder.append(']') +        return ''.join(array_builder) +    elif isinstance(something, dict): +        map_builder = [] +        map_builder.append('{' + struct.pack('!i', len(something))) +        for key, value in something.items(): +            map_builder.append('k' + struct.pack('!i', len(key)) + key) +            map_builder.append(_format_binary_recurse(value)) +        map_builder.append('}') +        return ''.join(map_builder) +    else: +        raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( +            type(something), something)) + + +def parse(something): +    try: +        if something.startswith('<?llsd/binary?>'): +            just_binary = something.split('\n', 1)[1] +            return LLSDBinaryParser().parse(just_binary) +        # This should be better. +        elif something.startswith('<'): +            return to_python(fromstring(something)[0]) +        else: +            return LLSDNotationParser().parse(something) +    except KeyError: +        raise Exception('LLSD could not be parsed: %s' % (something,)) + +class LLSD(object): +    def __init__(self, thing=None): +        self.thing = thing + +    def __str__(self): +        return self.toXML(self.thing) + +    parse = staticmethod(parse) +    toXML = staticmethod(format_xml) +    toBinary = staticmethod(format_binary) +    toNotation = staticmethod(format_notation) + + +undef = LLSD(None) + diff --git a/indra/lib/python/indra/base/lluuid.py b/indra/lib/python/indra/base/lluuid.py new file mode 100644 index 0000000000..a86ee03e72 --- /dev/null +++ b/indra/lib/python/indra/base/lluuid.py @@ -0,0 +1,265 @@ +"""\ +@file lluuid.py +@brief UUID parser/generator. + +Copyright (c) 2004-2007, Linden Research, Inc. +$License$ +""" + +import md5, random, socket, string, time, re + +def _int2binstr(i,l): +    s='' +    for a in range(l): +        s=chr(i&0xFF)+s +        i>>=8 +    return s + +def _binstr2int(s): +    i = long(0) +    for c in s: +        i = (i<<8) + ord(c) +    return i + +class UUID(object): +    """ +    A class which represents a 16 byte integer. Stored as a 16 byte 8 +    bit character string. + +    The string version is to be of the form: +    AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC  (a 128-bit number in hex) +    where A=network address, B=timestamp, C=random. +    """ + +    NULL_STR = "00000000-0000-0000-0000-000000000000" + +    # the UUIDREGEX_STRING is helpful for parsing UUID's in text +    hex_wildcard = r"[0-9a-fA-F]" +    word = hex_wildcard + r"{4,4}-" +    long_word = hex_wildcard + r"{8,8}-" +    very_long_word = hex_wildcard + r"{12,12}" +    UUID_REGEX_STRING = long_word + word + word + word + very_long_word +    uuid_regex = re.compile(UUID_REGEX_STRING) + +    rand = random.Random() +    ip = '' +    try: +        ip = socket.gethostbyname(socket.gethostname()) +    except(socket.gaierror): +        # no ip address, so just default to somewhere in 10.x.x.x +        ip = '10' +        for i in range(3): +            ip += '.' + str(rand.randrange(1,254)) +    hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) +    lastid = '' + +    def __init__(self, string_with_uuid=None): +        """ +        Initialize to first valid UUID in string argument, +        or to null UUID if none found or string is not supplied. +        """ +        self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +        if string_with_uuid: +            uuid_match = UUID.uuid_regex.search(string_with_uuid) +            if uuid_match: +                uuid_string = uuid_match.group() +                s = string.replace(uuid_string, '-', '') +                self._bits = _int2binstr(string.atol(s[:8],16),4) + \ +                             _int2binstr(string.atol(s[8:16],16),4) + \ +                             _int2binstr(string.atol(s[16:24],16),4) + \ +                             _int2binstr(string.atol(s[24:],16),4)  + +    def __len__(self): +        """ +        Used by the len() builtin. +        """ +        return 36 + +    def __nonzero__(self): +        return self._bits != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +    def __str__(self): +        uuid_string = self.toString() +        return uuid_string + +    __repr__ = __str__ + +    def __getitem__(self, index): +        return str(self)[index] + +    def __eq__(self, other): +        if isinstance(other, (str, unicode)): +            other = UUID(other) +        return self._bits == getattr(other, '_bits', '') + +    def __le__(self, other): +        return self._bits <= other._bits + +    def __ge__(self, other): +        return self._bits >= other._bits + +    def __lt__(self, other): +        return self._bits < other._bits + +    def __gt__(self, other): +        return self._bits > other._bits + +    def __hash__(self): +        return hash(self._bits) + +    def set(self, uuid): +        self._bits = uuid._bits + +    def setFromString(self, uuid_string): +        """ +        Given a string version of a uuid, set self bits +        appropriately. Returns self. +        """ +        s = string.replace(uuid_string, '-', '') +        self._bits = _int2binstr(string.atol(s[:8],16),4) + \ +                     _int2binstr(string.atol(s[8:16],16),4) + \ +                     _int2binstr(string.atol(s[16:24],16),4) + \ +                     _int2binstr(string.atol(s[24:],16),4)  +        return self + +    def setFromMemoryDump(self, gdb_string): +        """ +        We expect to get gdb_string as four hex units. eg: +        0x147d54db		0xc34b3f1b		0x714f989b		0x0a892fd2 +        Which will be translated to: +        db547d14-1b3f4bc3-9b984f71-d22f890a +        Returns self. +        """ +        s = string.replace(gdb_string, '0x', '') +        s = string.replace(s, ' ', '') +        t = '' +        for i in range(8,40,8): +            for j in range(0,8,2): +                t = t + s[i-j-2:i-j] +        self.setFromString(t) + +    def toString(self): +        """ +        Return as a string matching the LL standard +        AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC  (a 128-bit number in hex) +        where A=network address, B=timestamp, C=random. +        """ +        return uuid_bits_to_string(self._bits) + +    def getAsString(self): +        """ +        Return a different string representation of the form +        AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC	 (a 128-bit number in hex) +        where A=network address, B=timestamp, C=random. +        """ +        i1 = _binstr2int(self._bits[0:4]) +        i2 = _binstr2int(self._bits[4:8]) +        i3 = _binstr2int(self._bits[8:12]) +        i4 = _binstr2int(self._bits[12:16]) +        return '%08lx-%08lx-%08lx-%08lx' % (i1,i2,i3,i4) + +    def generate(self): +        """ +        Generate a new uuid. This algorithm is slightly different +        from c++ implementation for portability reasons. +        Returns self. +        """ +        newid = self.__class__.lastid +        while newid == self.__class__.lastid: +            now = long(time.time() * 1000) +            newid = ("%016x" % now) + self.__class__.hexip +            newid += ("%03x" % (self.__class__.rand.randrange(0,4095))) +        self.__class__.lastid = newid +        m = md5.new() +        m.update(newid) +        self._bits = m.digest() +        return self + +    def isNull(self): +        """ +        Returns 1 if the uuid is null - ie, equal to default uuid. +        """ +        return (self._bits == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") + +    def xor(self, rhs): +        """ +        xors self with rhs. +        """ +        v1 = _binstr2int(self._bits[0:4]) ^ _binstr2int(rhs._bits[0:4]) +        v2 = _binstr2int(self._bits[4:8]) ^ _binstr2int(rhs._bits[4:8]) +        v3 = _binstr2int(self._bits[8:12]) ^ _binstr2int(rhs._bits[8:12]) +        v4 = _binstr2int(self._bits[12:16]) ^ _binstr2int(rhs._bits[12:16]) +        self._bits = _int2binstr(v1,4) + \ +                     _int2binstr(v2,4) + \ +                     _int2binstr(v3,4) + \ +                     _int2binstr(v4,4)  + +def printTranslatedMemory(four_hex_uints): +    """ +    We expect to get the string as four hex units. eg: +    0x147d54db		0xc34b3f1b		0x714f989b		0x0a892fd2 +    Which will be translated to: +    db547d14-1b3f4bc3-9b984f71-d22f890a +    """ +    uuid = UUID() +    uuid.setFromMemoryDump(four_hex_uints) +    print uuid.toString() + +def isPossiblyID(id_str): +    """ +    This function returns 1 if the string passed has some uuid-like +    characteristics. Otherwise returns 0. +    """ +    if not id_str or len(id_str) <  5 or len(id_str) > 36: +        return 0 + +    if isinstance(id_str, UUID) or UUID.uuid_regex.match(id_str): +        return 1 +    # build a string which matches every character. +    hex_wildcard = r"[0-9a-fA-F]" +    chars = len(id_str) +    next = min(chars, 8) +    matcher = hex_wildcard+"{"+str(next)+","+str(next)+"}" +    chars = chars - next +    if chars > 0: +        matcher = matcher + "-" +        chars = chars - 1 +    for block in range(3): +        next = max(min(chars, 4), 0) +        if next: +            matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" +            chars = chars - next +        if chars > 0: +            matcher = matcher + "-" +            chars = chars - 1 +    if chars > 0: +        next = min(chars, 12) +        matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" +    #print matcher +    uuid_matcher = re.compile(matcher) +    if uuid_matcher.match(id_str): +        return 1 +    return 0 + +def uuid_bits_to_string(bits): +    i1 = _binstr2int(bits[0:4]) +    i2 = _binstr2int(bits[4:6]) +    i3 = _binstr2int(bits[6:8]) +    i4 = _binstr2int(bits[8:10]) +    i5 = _binstr2int(bits[10:12]) +    i6 = _binstr2int(bits[12:16]) +    return '%08lx-%04lx-%04lx-%04lx-%04lx%08lx' % (i1,i2,i3,i4,i5,i6) + +def uuid_bits_to_uuid(bits): +    return UUID(uuid_bits_to_string(bits)) + + +try: +    from mulib import mu +except ImportError: +    print "Couldn't import mulib, not registering UUID converter" +else: +    def convertUUID(req, uuid): +        return str(uuid) + +    mu.registerConverter(UUID, convertUUID) diff --git a/indra/lib/python/indra/ipc/__init__.py b/indra/lib/python/indra/ipc/__init__.py new file mode 100644 index 0000000000..6576513f9d --- /dev/null +++ b/indra/lib/python/indra/ipc/__init__.py @@ -0,0 +1,7 @@ +"""\ +@file __init__.py +@brief Initialization file for the indra ipc module. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" diff --git a/indra/lib/python/indra/ipc/compatibility.py b/indra/lib/python/indra/ipc/compatibility.py new file mode 100644 index 0000000000..a430e46ab4 --- /dev/null +++ b/indra/lib/python/indra/ipc/compatibility.py @@ -0,0 +1,103 @@ +"""\ +@file compatibility.py +@brief Classes that manage compatibility states. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + + +"""Compatibility combination table: + +    I   M   O   N   S +    --  --  --  --  -- +I:  I   I   I   I   I +M:  I   M   M   M   M    +O:  I   M   O   M   O +N:  I   M   M   N   N +S:  I   M   O   N   S + +""" + +class _Compatibility(object): +    def __init__(self, reason): +        self.reasons = [ ] +        if reason: +            self.reasons.append(reason) +         +    def combine(self, other): +        if self._level() <= other._level(): +            return self._buildclone(other) +        else: +            return other._buildclone(self) +     +    def prefix(self, leadin): +        self.reasons = [ leadin + r for r in self.reasons ]  +     +    def same(self):         return self._level() >=  1 +    def deployable(self):   return self._level() >   0 +    def resolved(self):     return self._level() >  -1 +    def compatible(self):   return self._level() >  -2 +     +    def explain(self): +        return self.__class__.__name__ + "\n" + "\n".join(self.reasons) + "\n" +         +    def _buildclone(self, other=None): +        c = self._buildinstance() +        c.reasons = self.reasons +        if other: +            c.reasons = c.reasons + other.reasons +        return c +         +    def _buildinstance(self): +        return self.__class__(None) + +#    def _level(self): +#        raise RuntimeError('implement in subclass') + + +class Incompatible(_Compatibility): +    def _level(self): +        return -2 + +class Mixed(_Compatibility): +    def __init__(self, *inputs): +        _Compatibility.__init__(self, None) +        for i in inputs: +            self.reasons += i.reasons +                     +    def _buildinstance(self): +        return self.__class__() +         +    def _level(self): +        return -1 + +class _Aged(_Compatibility): +    def combine(self, other): +        if self._level() == other._level(): +            return self._buildclone(other) +        if int(self._level()) == int(other._level()): +            return Mixed(self, other) +        return _Compatibility.combine(self, other) + +class Older(_Aged): +    def _level(self): +        return -0.25 +     +class Newer(_Aged): +    def _level(self): +        return 0.25 + +class Same(_Compatibility): +    def __init__(self): +        _Compatibility.__init__(self, None) +     +    def _buildinstance(self): +        return self.__class__() +         +    def _level(self): +        return 1 + + + + diff --git a/indra/lib/python/indra/ipc/httputil.py b/indra/lib/python/indra/ipc/httputil.py new file mode 100644 index 0000000000..85e4a645bc --- /dev/null +++ b/indra/lib/python/indra/ipc/httputil.py @@ -0,0 +1,94 @@ +"""\ +@file httputil.py +@brief HTTP utilities. HTTP date conversion and non-blocking HTTP +client support. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" + + +import os +import time +import urlparse + +import httplib +try: +    from mx.DateTime import Parser + +    parse_date = Parser.DateTimeFromString +except ImportError: +    from dateutil import parser + +    parse_date = parser.parse + + +HTTP_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT' + + +to_http_time = lambda t: time.strftime(HTTP_TIME_FORMAT, time.gmtime(t)) +from_http_time = lambda t: int(parse_date(t).gmticks()) + +def host_and_port_from_url(url): +    """@breif Simple function to get host and port from an http url. +    @return Returns host, port and port may be None. +    """ +    host = None +    port = None +    parsed_url = urlparse.urlparse(url) +    try: +        host, port = parsed_url[1].split(':') +    except ValueError: +        host = parsed_url[1].split(':') +    return host, port + + +def better_putrequest(self, method, url, skip_host=0): +    self.method = method +    self.path = url +    self.old_putrequest(method, url, skip_host) + + +class HttpClient(httplib.HTTPConnection): +    """A subclass of httplib.HTTPConnection which works around a bug +    in the interaction between eventlet sockets and httplib. httplib relies +    on gc to close the socket, causing the socket to be closed too early. + +    This is an awful hack and the bug should be fixed properly ASAP. +    """ +    def __init__(self, host, port=None, strict=None): +       httplib.HTTPConnection.__init__(self, host, port, strict) + +    def close(self): +        pass + +    old_putrequest = httplib.HTTPConnection.putrequest +    putrequest = better_putrequest + + +class HttpsClient(httplib.HTTPSConnection): +    def close(self): +        pass +    old_putrequest = httplib.HTTPSConnection.putrequest +    putrequest = better_putrequest + + + +scheme_to_factory_map = { +    'http': HttpClient, +    'https': HttpsClient, +} + + +def makeConnection(scheme, location, use_proxy): +    if use_proxy: +        if "http_proxy" in os.environ: +            location = os.environ["http_proxy"] +        elif "ALL_PROXY" in os.environ: +            location = os.environ["ALL_PROXY"] +        else: +            location = "localhost:3128" #default to local squid +        if location.startswith("http://"): +            location = location[len("http://"):] +    return scheme_to_factory_map[scheme](location) + diff --git a/indra/lib/python/indra/ipc/llmessage.py b/indra/lib/python/indra/ipc/llmessage.py new file mode 100644 index 0000000000..5a9e867ecd --- /dev/null +++ b/indra/lib/python/indra/ipc/llmessage.py @@ -0,0 +1,353 @@ +"""\ +@file llmessage.py +@brief Message template parsing and compatiblity + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +from sets import Set, ImmutableSet + +from compatibility import Incompatible, Older, Newer, Same +from tokenstream import TokenStream + +### +### Message Template +### + +class Template: +    def __init__(self): +        self.messages = { } +     +    def addMessage(self, m): +        self.messages[m.name] = m +     +    def compatibleWithBase(self, base): +        messagenames = ( +              ImmutableSet(self.messages.keys()) +            | ImmutableSet(base.messages.keys()) +            ) +             +        compatibility = Same() +        for name in messagenames: +            selfmessage = self.messages.get(name, None) +            basemessage = base.messages.get(name, None) +             +            if not selfmessage: +                c = Older("missing message %s, did you mean to deprecate?" % name) +            elif not basemessage: +                c = Newer("added message %s" % name) +            else: +                c = selfmessage.compatibleWithBase(basemessage) +                c.prefix("in message %s: " % name) +             +            compatibility = compatibility.combine(c) +         +        return compatibility + + + +class Message: +    HIGH = "High" +    MEDIUM = "Medium" +    LOW = "Low" +    FIXED = "Fixed" +    priorities = [ HIGH, MEDIUM, LOW, FIXED ] +    prioritieswithnumber = [ FIXED ] +     +    TRUSTED = "Trusted" +    NOTTRUSTED = "NotTrusted" +    trusts = [ TRUSTED, NOTTRUSTED ] +     +    UNENCODED = "Unencoded" +    ZEROCODED = "Zerocoded" +    encodings = [ UNENCODED, ZEROCODED ] + +    NOTDEPRECATED = "NotDeprecated" +    DEPRECATED = "Deprecated" +    UDPDEPRECATED = "UDPDeprecated" +    deprecations = [ NOTDEPRECATED, UDPDEPRECATED, DEPRECATED ] +        # in order of increasing deprecation +     +    def __init__(self, name, number, priority, trust, coding): +        self.name = name +        self.number = number +        self.priority = priority +        self.trust = trust +        self.coding = coding +        self.deprecateLevel = 0 +        self.blocks = [ ] + +    def deprecated(self): +        return self.deprecateLevel != 0 + +    def deprecate(self, deprecation):  +        self.deprecateLevel = self.deprecations.index(deprecation) + +    def addBlock(self, block): +        self.blocks.append(block) +         +    def compatibleWithBase(self, base): +        if self.name != base.name: +            # this should never happen in real life because of the +            # way Template matches up messages by name +            return Incompatible("has different name: %s vs. %s in base" +                               % (self.name, base.name))  +        if self.priority != base.priority: +            return Incompatible("has different priority: %s vs. %s in base" +                                % (self.priority, base.priority))  +        if self.trust != base.trust: +            return Incompatible("has different trust: %s vs. %s in base" +                                % (self.trust, base.trust))  +        if self.coding != base.coding: +            return Incompatible("has different coding: %s vs. %s in base" +                                % (self.coding, base.coding))  +        if self.number != base.number: +            return Incompatible("has different number: %s vs. %s in base" +                                % (self.number, base.number)) +         +        compatibility = Same() + +        if self.deprecateLevel != base.deprecateLevel: +            if self.deprecateLevel < base.deprecateLevel: +                c = Older("is less deprecated: %s vs. %s in base" % ( +                    self.deprecations[self.deprecateLevel], +                    self.deprecations[base.deprecateLevel])) +            else: +                c = Newer("is more deprecated: %s vs. %s in base" % ( +                    self.deprecations[self.deprecateLevel], +                    self.deprecations[base.deprecateLevel])) +            compatibility = compatibility.combine(c) +         +        selflen = len(self.blocks) +        baselen = len(base.blocks) +        samelen = min(selflen, baselen) +             +        for i in xrange(0, samelen): +            selfblock = self.blocks[i] +            baseblock = base.blocks[i] +             +            c = selfblock.compatibleWithBase(baseblock) +            if not c.same(): +                c = Incompatible("block %d isn't identical" % i) +            compatibility = compatibility.combine(c) +         +        if selflen > baselen: +            c = Newer("has %d extra blocks" % (selflen - baselen)) +        elif selflen < baselen: +            c = Older("missing %d extra blocks" % (baselen - selflen)) +        else: +            c = Same() +         +        compatibility = compatibility.combine(c) +        return compatibility + + + +class Block(object): +    SINGLE = "Single" +    MULTIPLE = "Multiple" +    VARIABLE = "Variable" +    repeats = [ SINGLE, MULTIPLE, VARIABLE ] +    repeatswithcount = [ MULTIPLE ] +     +    def __init__(self, name, repeat, count=None): +        self.name = name +        self.repeat = repeat +        self.count = count +        self.variables = [ ] + +    def addVariable(self, variable): +        self.variables.append(variable) +         +    def compatibleWithBase(self, base): +        if self.name != base.name: +            return Incompatible("has different name: %s vs. %s in base" +                                % (self.name, base.name)) +        if self.repeat != base.repeat: +            return Incompatible("has different repeat: %s vs. %s in base" +                                % (self.repeat, base.repeat)) +        if self.repeat in Block.repeatswithcount: +            if self.count != base.count: +                return Incompatible("has different count: %s vs. %s in base" +                                    % (self.count, base.count))  + +        compatibility = Same() +         +        selflen = len(self.variables) +        baselen = len(base.variables) +         +        for i in xrange(0, min(selflen, baselen)): +            selfvar = self.variables[i] +            basevar = base.variables[i] +             +            c = selfvar.compatibleWithBase(basevar) +            if not c.same(): +                c = Incompatible("variable %d isn't identical" % i) +            compatibility = compatibility.combine(c) + +        if selflen > baselen: +            c = Newer("has %d extra variables" % (selflen - baselen)) +        elif selflen < baselen: +            c = Older("missing %d extra variables" % (baselen - selflen)) +        else: +            c = Same() + +        compatibility = compatibility.combine(c) +        return compatibility + + + +class Variable: +    U8 = "U8"; U16 = "U16"; U32 = "U32"; U64 = "U64" +    S8 = "S8"; S16 = "S16"; S32 = "S32"; S64 = "S64" +    F32 = "F32"; F64 = "F64" +    LLVECTOR3 = "LLVector3"; LLVECTOR3D = "LLVector3d"; LLVECTOR4 = "LLVector4" +    LLQUATERNION = "LLQuaternion" +    LLUUID = "LLUUID" +    BOOL = "BOOL" +    IPADDR = "IPADDR"; IPPORT = "IPPORT" +    FIXED = "Fixed" +    VARIABLE = "Variable" +    types = [ U8, U16, U32, U64, S8, S16, S32, S64, F32, F64, +                LLVECTOR3, LLVECTOR3D, LLVECTOR4, LLQUATERNION, +                LLUUID, BOOL, IPADDR, IPPORT, FIXED, VARIABLE ] +    typeswithsize = [ FIXED, VARIABLE ] +     +    def __init__(self, name, type, size): +        self.name = name +        self.type = type +        self.size = size +         +    def compatibleWithBase(self, base): +        if self.name != base.name: +            return Incompatible("has different name: %s vs. %s in base" +                                % (self.name, base.name)) +        if self.type != base.type: +            return Incompatible("has different type: %s vs. %s in base" +                                % (self.type, base.type)) +        if self.type in Variable.typeswithsize: +            if self.size != base.size: +                return Incompatible("has different size: %s vs. %s in base" +                                    % (self.size, base.size))  +        return Same() + + + +### +### Parsing Message Templates +### + +class TemplateParser: +    def __init__(self, tokens): +        self._tokens = tokens +        self._version = 0 +        self._numbers = { } +        for p in Message.priorities: +            self._numbers[p] = 0 + +    def parseTemplate(self): +        tokens = self._tokens +        t = Template() +        while True: +            if tokens.want("version"): +                v = float(tokens.require(tokens.wantFloat())) +                self._version = v +                t.version = v +                continue +     +            m = self.parseMessage() +            if m: +                t.addMessage(m) +                continue +             +            if self._version >= 2.0: +                tokens.require(tokens.wantEOF()) +                break +            else: +                if tokens.wantEOF(): +                    break +             +                tokens.consume() +                    # just assume (gulp) that this is a comment +                    # line 468: "sim -> dataserver" +        return t                 + + +    def parseMessage(self): +        tokens = self._tokens +        if not tokens.want("{"): +            return None +         +        name     = tokens.require(tokens.wantSymbol()) +        priority = tokens.require(tokens.wantOneOf(Message.priorities)) +         +        if self._version >= 2.0  or  priority in Message.prioritieswithnumber: +            number = int("+" + tokens.require(tokens.wantInteger()), 0) +        else: +            self._numbers[priority] += 1 +            number = self._numbers[priority] +         +        trust    = tokens.require(tokens.wantOneOf(Message.trusts)) +        coding   = tokens.require(tokens.wantOneOf(Message.encodings)) +     +        m = Message(name, number, priority, trust, coding) +         +        if self._version >= 2.0: +            d = tokens.wantOneOf(Message.deprecations) +            if d: +                m.deprecate(d) +                 +        while True: +            b = self.parseBlock() +            if not b: +                break +            m.addBlock(b) +             +        tokens.require(tokens.want("}")) +         +        return m +     +     +    def parseBlock(self): +        tokens = self._tokens +        if not tokens.want("{"): +            return None +        name = tokens.require(tokens.wantSymbol()) +        repeat = tokens.require(tokens.wantOneOf(Block.repeats)) +        if repeat in Block.repeatswithcount: +            count = int(tokens.require(tokens.wantInteger())) +        else: +            count = None +     +        b = Block(name, repeat, count) +     +        while True: +            v = self.parseVariable() +            if not v: +                break +            b.addVariable(v) +             +        tokens.require(tokens.want("}")) +        return b +             +     +    def parseVariable(self): +        tokens = self._tokens +        if not tokens.want("{"): +            return None +        name = tokens.require(tokens.wantSymbol()) +        type = tokens.require(tokens.wantOneOf(Variable.types)) +        if type in Variable.typeswithsize: +            size = tokens.require(tokens.wantInteger()) +        else: +            tokens.wantInteger() # in LandStatRequest: "{ ParcelLocalID S32 1 }"  +            size = None +        tokens.require(tokens.want("}")) +        return Variable(name, type, size) +         +def parseTemplateString(s): +    return TemplateParser(TokenStream().fromString(s)).parseTemplate() + +def parseTemplateFile(f): +    return TemplateParser(TokenStream().fromFile(f)).parseTemplate() diff --git a/indra/lib/python/indra/ipc/llsdhttp.py b/indra/lib/python/indra/ipc/llsdhttp.py new file mode 100644 index 0000000000..a2a889742a --- /dev/null +++ b/indra/lib/python/indra/ipc/llsdhttp.py @@ -0,0 +1,257 @@ +"""\ +@file llsdhttp.py +@brief Functions to ease moving llsd over http + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" + +import os.path +import os +import os +import urlparse + +from indra.base import llsd +from indra.ipc import httputil +LLSD = llsd.LLSD() + + +class ConnectionError(Exception): +    def __init__(self, method, host, port, path, status, reason, body): +        self.method = method +        self.host = host +        self.port = port +        self.path = path +        self.status = status +        self.reason = reason +        self.body = body +        Exception.__init__(self) + +    def __str__(self): +        return "%s(%r, %r, %r, %r, %r, %r, %r)" % ( +            type(self).__name__, +            self.method, self.host, self.port, +            self.path, self.status, self.reason, self.body) + + +class NotFound(ConnectionError): +    pass + +class Forbidden(ConnectionError): +    pass + +class MalformedResponse(ConnectionError): +    pass + +class NotImplemented(ConnectionError): +    pass + +def runConnection(connection, raise_parse_errors=True): +    response = connection.getresponse() +    if (response.status not in [200, 201, 204]): +        klass = {404:NotFound, +                 403:Forbidden, +                 501:NotImplemented}.get(response.status, ConnectionError) +        raise klass( +            connection.method, connection.host, connection.port, +            connection.path, response.status, response.reason, response.read()) +    content_length = response.getheader('content-length') + +    if content_length: # Check to see whether it is not None +        content_length = int(content_length) + +    if content_length: # Check to see whether the length is not 0 +        body = response.read(content_length) +    else: +        body = '' + +    if not body.strip(): +        #print "No body:", `body` +        return None +    try: +        return LLSD.parse(body) +    except Exception, e: +        if raise_parse_errors: +            print "Exception: %s, Could not parse LLSD: " % (e), body +            raise MalformedResponse( +            connection.method, connection.host, connection.port, +            connection.path, response.status, response.reason, body) +        else: +            return None + +class FileScheme(object): +    """Retarded scheme to local file wrapper.""" +    host = '<file>' +    port = '<file>' +    reason = '<none>' + +    def __init__(self, location): +        pass + +    def request(self, method, fullpath, body='', headers=None): +        self.status = 200 +        self.path = fullpath.split('?')[0] +        self.method = method = method.lower() +        assert method in ('get', 'put', 'delete') +        if method == 'delete': +            try: +                os.remove(self.path) +            except OSError: +                pass  # don't complain if already deleted +        elif method == 'put': +            try: +                f = file(self.path, 'w') +                f.write(body) +                f.close() +            except IOError, e: +                self.status = 500 +                self.raise_connection_error() +        elif method == 'get': +            if not os.path.exists(self.path): +                self.status = 404 +                self.raise_connection_error(NotFound) + +    def connect(self): +        pass + +    def getresponse(self): +        return self + +    def getheader(self, header): +        if header == 'content-length': +            try: +                return os.path.getsize(self.path) +            except OSError: +                return 0 + +    def read(self, howmuch=None): +        if self.method == 'get': +            try: +                return file(self.path, 'r').read(howmuch) +            except IOError: +                self.status = 500 +                self.raise_connection_error() +        return '' + +    def raise_connection_error(self, klass=ConnectionError): +        raise klass( +            self.method, self.host, self.port, +            self.path, self.status, self.reason, '') + +scheme_to_factory_map = { +    'http': httputil.HttpClient, +    'https': httputil.HttpsClient, +    'file': FileScheme, +} + +def makeConnection(scheme, location, use_proxy): +    if use_proxy: +        if "http_proxy" in os.environ: +            location = os.environ["http_proxy"] +        elif "ALL_PROXY" in os.environ: +            location = os.environ["ALL_PROXY"] +        else: +            location = "localhost:3128" #default to local squid +        if location.startswith("http://"): +            location = location[len("http://"):] +    return scheme_to_factory_map[scheme](location) + + +def get(url, headers=None, use_proxy=False): +    if headers is None: +        headers = {} +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=use_proxy) +    fullpath = path +    if query: +        fullpath += "?"+query +    connection.connect() +    if headers is None: +        headers=dict() +    if use_proxy: +        headers.update({ "Host" : location }) +        connection.request("GET", url, headers=headers) +    else: +        connection.request("GET", fullpath, headers=headers) + +    return runConnection(connection) + +def put(url, data, headers=None): +    body = LLSD.toXML(data) +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    if headers is None: +        headers = {} +    headers.update({'Content-Type': 'application/xml'}) +    fullpath = path +    if query: +        fullpath += "?"+query +    connection.request("PUT", fullpath, body, headers) +    return runConnection(connection, raise_parse_errors=False) + +def delete(url): +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("DELETE", path+"?"+query) +    return runConnection(connection, raise_parse_errors=False) + +def post(url, data=None): +    body = LLSD.toXML(data) +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/xml'}) +    return runConnection(connection) + +def postFile(url, filename): +    f = open(filename) +    body = f.read() +    f.close() +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'}) +    return runConnection(connection) + +def getStatus(url, use_proxy=False): +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=use_proxy) +    if use_proxy: +        connection.request("GET", url, headers={ "Host" : location }) +    else: +        connection.request("GET", path+"?"+query) +    return connection.getresponse().status + +def putStatus(url, data): +    body = LLSD.toXML(data) +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("PUT", path+"?"+query, body, {'Content-Type': 'application/xml'}) +    return connection.getresponse().status + +def deleteStatus(url): +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("DELETE", path+"?"+query) +    return connection.getresponse().status + +def postStatus(url, data): +    body = LLSD.toXML(data) +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("POST", path+"?"+query, body) +    return connection.getresponse().status + +def postFileStatus(url, filename): +    f = open(filename) +    body = f.read() +    f.close() +    scheme, location, path, params, query, id = urlparse.urlparse(url) +    connection = makeConnection(scheme, location, use_proxy=False) +    connection.request("POST", path+"?"+query, body, {'Content-Type': 'application/octet-stream'}) +    response = connection.getresponse() +    return response.status, response.read() + +def getFromSimulator(path, use_proxy=False): +    return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy) + +def postToSimulator(path, data=None): +    return post('http://' + simulatorHostAndPort + path, data) diff --git a/indra/lib/python/indra/ipc/russ.py b/indra/lib/python/indra/ipc/russ.py new file mode 100644 index 0000000000..f6f67d4314 --- /dev/null +++ b/indra/lib/python/indra/ipc/russ.py @@ -0,0 +1,123 @@ +"""\ +@file russ.py +@brief Recursive URL Substitution Syntax helpers +@author Phoenix + +Many details on how this should work is available on the wiki: +https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax + +Adding features to this should be reflected in that page in the +implementations section. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +import urllib +from indra.ipc import llsdhttp + +class UnbalancedBraces(Exception): +    pass + +class UnknownDirective(Exception): +    pass + +class BadDirective(Exception): +    pass + +def format(format_str, context): +    """@brief Format format string according to rules for RUSS. +@see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax +@param format_str The input string to format. +@param context A map used for string substitutions. +@return Returns the formatted string. If no match, the braces remain intact. +""" +    while True: +        #print "format_str:", format_str +        all_matches = _find_sub_matches(format_str) +        if not all_matches: +            break +        substitutions = 0 +        while True: +            matches = all_matches.pop() +            # we work from right to left to make sure we do not +            # invalidate positions earlier in format_str +            matches.reverse() +            for pos in matches: +                # Use index since _find_sub_matches should have raised +                # an exception, and failure to find now is an exception. +                end = format_str.index('}', pos) +                #print "directive:", format_str[pos+1:pos+5] +                if format_str[pos + 1] == '$': +                    value = context.get(format_str[pos + 2:end]) +                elif format_str[pos + 1] == '%': +                    value = _build_query_string( +                        context.get(format_str[pos + 2:end])) +                elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file': +                    value = _fetch_url_directive(format_str[pos + 1:end]) +                else: +                    raise UnknownDirective, format_str[pos:end + 1] +                if not value == None: +                    format_str = format_str[:pos]+str(value)+format_str[end+1:] +                    substitutions += 1 + +            # If there were any substitutions at this depth, re-parse +            # since this may have revealed new things to substitute +            if substitutions: +                break +            if not all_matches: +                break + +        # If there were no substitutions at all, and we have exhausted +        # the possible matches, bail. +        if not substitutions: +            break +    return format_str + +def _find_sub_matches(format_str): +    """@brief Find all of the substitution matches. +@param format_str the RUSS conformant format string.	 +@return Returns an array of depths of arrays of positional matches in input. +""" +    depth = 0 +    matches = [] +    for pos in range(len(format_str)): +        if format_str[pos] == '{': +            depth += 1 +            if not len(matches) == depth: +                matches.append([]) +            matches[depth - 1].append(pos) +            continue +        if format_str[pos] == '}': +            depth -= 1 +            continue +    if not depth == 0: +        raise UnbalancedBraces, format_str +    return matches + +def _build_query_string(query_dict): +    """\ +    @breif given a dict, return a query string. utility wrapper for urllib. +    @param query_dict input query dict +    @returns Returns an urlencoded query string including leading '?'. +    """ +    if query_dict: +        return '?' + urllib.urlencode(query_dict) +    else: +        return '' + +def _fetch_url_directive(directive): +    "*FIX: This only supports GET" +    commands = directive.split('|') +    resource = llsdhttp.get(commands[0]) +    if len(commands) == 3: +        resource = _walk_resource(resource, commands[2]) +    return resource + +def _walk_resource(resource, path): +    path = path.split('/') +    for child in path: +        if not child: +            continue +        resource = resource[child] +    return resource diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py new file mode 100644 index 0000000000..d422b2ed42 --- /dev/null +++ b/indra/lib/python/indra/ipc/servicebuilder.py @@ -0,0 +1,52 @@ +"""\ +@file servicebuilder.py +@author Phoenix +@brief Class which will generate service urls. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +from indra.base import config +from indra.ipc import llsdhttp +from indra.ipc import russ + +services_config = {} +try: +    services_config = llsdhttp.get(config.get('services-config')) +except: +    pass + +class ServiceBuilder(object): +    def __init__(self, services_definition = services_config): +        """\ +        @brief +        @brief Create a ServiceBuilder. +        @param services_definition Complete services definition, services.xml. +        """ +        # no need to keep a copy of the services section of the +        # complete services definition, but it doesn't hurt much. +        self.services = services_definition['services'] +        self.builders = {} +        for service in self.services: +            service_builder = service.get('service-builder') +            if not service_builder: +                continue +            if isinstance(service_builder, dict): +                # We will be constructing several builders +                for name, builder in service_builder.items(): +                    full_builder_name = service['name'] + '-' + name +                    self.builders[full_builder_name] = builder +            else: +                self.builders[service['name']] = service_builder + +    def buildServiceURL(self, name, context): +        """\ +        @brief given the environment on construction, return a service URL. +        @param name The name of the service. +        @param context A dict of name value lookups for the service. +        @returns Returns the  +        """ +        base_url = config.get('services-base-url') +        svc_path = russ.format(self.builders[name], context) +        return base_url + svc_path diff --git a/indra/lib/python/indra/ipc/tokenstream.py b/indra/lib/python/indra/ipc/tokenstream.py new file mode 100644 index 0000000000..f757f2f8af --- /dev/null +++ b/indra/lib/python/indra/ipc/tokenstream.py @@ -0,0 +1,134 @@ +"""\ +@file tokenstream.py +@brief Message template parsing utility class + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +import re + +class _EOF(object): +    pass + +EOF = _EOF() + +class _LineMarker(int): +    pass +     +_commentRE = re.compile(r'//.*') +_symbolRE = re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*') +_integerRE = re.compile(r'(0x[0-9A-Fa-f]+|0\d*|[1-9]\d*)') +_floatRE = re.compile(r'\d+(\.\d*)?') + + +class ParseError(Exception): +    def __init__(self, stream, reason): +        self.line = stream.line +        self.context = stream._context() +        self.reason = reason + +    def _contextString(self):     +        c = [ ] +        for t in self.context: +            if isinstance(t, _LineMarker): +                break +            c.append(t) +        return " ".join(c) + +    def __str__(self): +        return "line %d: %s @ ... %s" % ( +            self.line, self.reason, self._contextString()) + +    def __nonzero__(self): +        return False + + +def _optionText(options): +    n = len(options) +    if n == 1: +        return '"%s"' % options[0] +    return '"' + '", "'.join(options[0:(n-1)]) + '" or "' + options[-1] + '"' + + +class TokenStream(object): +    def __init__(self): +        self.line = 0 +        self.tokens = [ ] +     +    def fromString(self, string): +        return self.fromLines(string.split('\n')) +         +    def fromFile(self, file): +        return self.fromLines(file) + +    def fromLines(self, lines): +        i = 0 +        for line in lines: +            i += 1 +            self.tokens.append(_LineMarker(i)) +            self.tokens.extend(_commentRE.sub(" ", line).split()) +        self._consumeLines() +        return self +     +    def consume(self): +        if not self.tokens: +            return EOF +        t = self.tokens.pop(0) +        self._consumeLines() +        return t +     +    def _consumeLines(self): +        while self.tokens and isinstance(self.tokens[0], _LineMarker): +            self.line = self.tokens.pop(0) +     +    def peek(self): +        if not self.tokens: +            return EOF +        return self.tokens[0] +             +    def want(self, t): +        if t == self.peek(): +            return self.consume() +        return ParseError(self, 'expected "%s"' % t) + +    def wantOneOf(self, options): +        assert len(options) +        if self.peek() in options: +            return self.consume() +        return ParseError(self, 'expected one of %s' % _optionText(options)) + +    def wantEOF(self): +        return self.want(EOF) +         +    def wantRE(self, re, message=None): +        t = self.peek() +        if t != EOF: +            m = re.match(t) +            if m and m.end() == len(t): +                return self.consume() +        if not message: +            message = "expected match for r'%s'" % re.pattern +        return ParseError(self, message) +     +    def wantSymbol(self): +        return self.wantRE(_symbolRE, "expected symbol") +     +    def wantInteger(self): +        return self.wantRE(_integerRE, "expected integer") +     +    def wantFloat(self): +        return self.wantRE(_floatRE, "expected float") +     +    def _context(self): +        n = min(5, len(self.tokens)) +        return self.tokens[0:n] + +    def require(self, t): +        if t: +            return t +        if isinstance(t, ParseError): +            raise t +        else: +            raise ParseError(self, "unmet requirement") + diff --git a/indra/lib/python/indra/ipc/xml_rpc.py b/indra/lib/python/indra/ipc/xml_rpc.py new file mode 100644 index 0000000000..66375b4ac3 --- /dev/null +++ b/indra/lib/python/indra/ipc/xml_rpc.py @@ -0,0 +1,253 @@ +"""\ +@file xml_rpc.py +@brief An implementation of a parser/generator for the XML-RPC xml format. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" + + +from greenlet import greenlet + +from mulib import mu + +from xml.sax import handler +from xml.sax import parseString + + +# States +class Expected(object): +    def __init__(self, tag): +        self.tag = tag + +    def __getattr__(self, name): +        return type(self)(name) + +    def __repr__(self): +        return '%s(%r)' % ( +            type(self).__name__, self.tag) + + +class START(Expected): +    pass + + +class END(Expected): +    pass + + +class STR(object): +    tag = '' + + +START = START('') +END = END('') + + +class Malformed(Exception): +    pass + + +class XMLParser(handler.ContentHandler): +    def __init__(self, state_machine, next_states): +        handler.ContentHandler.__init__(self) +        self.state_machine = state_machine +        if not isinstance(next_states, tuple): +            next_states = (next_states, ) +        self.next_states = next_states +        self._character_buffer = '' + +    def assertState(self, state, name, *rest): +        if not isinstance(self.next_states, tuple): +            self.next_states = (self.next_states, ) +        for next in self.next_states: +            if type(state) == type(next): +                if next.tag and next.tag != name: +                    raise Malformed( +                        "Expected %s, got %s %s %s" % ( +                            next, state, name, rest)) +                break +        else: +            raise Malformed( +                "Expected %s, got %s %s %s" % ( +                    self.next_states, state, name, rest)) + +    def startElement(self, name, attrs): +        self.assertState(START, name.lower(), attrs) +        self.next_states = self.state_machine.switch(START, (name.lower(), dict(attrs))) + +    def endElement(self, name): +        if self._character_buffer.strip(): +            characters = self._character_buffer.strip() +            self._character_buffer = '' +            self.assertState(STR, characters) +            self.next_states = self.state_machine.switch(characters) +        self.assertState(END, name.lower()) +        self.next_states = self.state_machine.switch(END, name.lower()) + +    def error(self, exc): +        self.bozo = 1 +        self.exc = exc + +    def fatalError(self, exc): +        self.error(exc) +        raise exc + +    def characters(self, characters): +        self._character_buffer += characters + + +def parse(what): +    child = greenlet(xml_rpc) +    me = greenlet.getcurrent() +    startup_states = child.switch(me) +    parser = XMLParser(child, startup_states) +    try: +        parseString(what, parser) +    except Malformed: +        print what +        raise +    return child.switch() + + +def xml_rpc(yielder): +    yielder.switch(START.methodcall) +    yielder.switch(START.methodname) +    methodName = yielder.switch(STR) +    yielder.switch(END.methodname) + +    yielder.switch(START.params) + +    root = None +    params = [] +    while True: +        state, _ = yielder.switch(START.param, END.params) +        if state == END: +            break + +        yielder.switch(START.value) +         +        params.append( +            handle(yielder)) + +        yielder.switch(END.value) +        yielder.switch(END.param) + +    yielder.switch(END.methodcall) +    ## Resume parse +    yielder.switch() +    ## Return result to parse +    return methodName.strip(), params + + +def handle(yielder): +    _, (tag, attrs) = yielder.switch(START) +    if tag in ['int', 'i4']: +        result = int(yielder.switch(STR)) +    elif tag == 'boolean': +        result = bool(int(yielder.switch(STR))) +    elif tag == 'string': +        result = yielder.switch(STR) +    elif tag == 'double': +        result = float(yielder.switch(STR)) +    elif tag == 'datetime.iso8601': +        result = yielder.switch(STR) +    elif tag == 'base64': +        result = base64.b64decode(yielder.switch(STR)) +    elif tag == 'struct': +        result = {} +        while True: +            state, _ = yielder.switch(START.member, END.struct) +            if state == END: +                break + +            yielder.switch(START.name) +            key = yielder.switch(STR) +            yielder.switch(END.name) + +            yielder.switch(START.value) +            result[key] = handle(yielder) +            yielder.switch(END.value) + +            yielder.switch(END.member) +        ## We already handled </struct> above, don't want to handle it below +        return result +    elif tag == 'array': +        result = [] +        yielder.switch(START.data) +        while True: +            state, _ = yielder.switch(START.value, END.data) +            if state == END: +                break + +            result.append(handle(yielder)) + +            yielder.switch(END.value) + +    yielder.switch(getattr(END, tag)) + +    return result + + +VALUE = mu.tagFactory('value') +BOOLEAN = mu.tagFactory('boolean') +INT = mu.tagFactory('int') +STRUCT = mu.tagFactory('struct') +MEMBER = mu.tagFactory('member') +NAME = mu.tagFactory('name') +ARRAY = mu.tagFactory('array') +DATA = mu.tagFactory('data') +STRING = mu.tagFactory('string') +DOUBLE = mu.tagFactory('double') +METHODRESPONSE = mu.tagFactory('methodResponse') +PARAMS = mu.tagFactory('params') +PARAM = mu.tagFactory('param') + +mu.inline_elements['string'] = True +mu.inline_elements['boolean'] = True +mu.inline_elements['name'] = True + + +def _generate(something): +    if isinstance(something, dict): +        result = STRUCT() +        for key, value in something.items(): +            result[ +                MEMBER[ +                    NAME[key], _generate(value)]] +        return VALUE[result] +    elif isinstance(something, list): +        result = DATA() +        for item in something: +            result[_generate(item)] +        return VALUE[ARRAY[[result]]] +    elif isinstance(something, basestring): +        return VALUE[STRING[something]] +    elif isinstance(something, bool): +        if something: +            return VALUE[BOOLEAN['1']] +        return VALUE[BOOLEAN['0']] +    elif isinstance(something, int): +        return VALUE[INT[something]] +    elif isinstance(something, float): +        return VALUE[DOUBLE[something]] + +def generate(*args): +    params = PARAMS() +    for arg in args: +        params[PARAM[_generate(arg)]] +    return METHODRESPONSE[params] + + +if __name__ == '__main__': +    print parse("""<?xml version="1.0"?> <methodCall>  <methodName>examples.getStateName</methodName>  <params>  <param>  <value><i4>41</i4></value>  </param>  </params>  </methodCall> +""") +     +         +         +         +         +         +         +         +         diff --git a/indra/lib/python/indra/util/__init__.py b/indra/lib/python/indra/util/__init__.py new file mode 100644 index 0000000000..c8b1848dfb --- /dev/null +++ b/indra/lib/python/indra/util/__init__.py @@ -0,0 +1,7 @@ +"""\ +@file __init__.py +@brief Initialization file for the indra util module. + +Copyright (c) 2006-2007, Linden Research, Inc. +$License$ +""" diff --git a/indra/lib/python/indra/util/helpformatter.py b/indra/lib/python/indra/util/helpformatter.py new file mode 100644 index 0000000000..539669f665 --- /dev/null +++ b/indra/lib/python/indra/util/helpformatter.py @@ -0,0 +1,32 @@ +"""\ +@file helpformatter.py +@author Phoenix +@brief Class for formatting optparse descriptions. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +import optparse +import textwrap + +class Formatter(optparse.IndentedHelpFormatter): +    def __init__( +        self,  +        p_indentIncrement = 2,  +        p_maxHelpPosition = 24, +        p_width = 79, +        p_shortFirst = 1) : +        optparse.HelpFormatter.__init__( +            self,  +            p_indentIncrement, +            p_maxHelpPosition,  +            p_width,  +            p_shortFirst) +    def format_description(self, p_description): +        t_descWidth = self.width - self.current_indent +        t_indent = " " * (self.current_indent + 2) +        return "\n".join( +            [textwrap.fill(descr, t_descWidth, initial_indent = t_indent, +                           subsequent_indent = t_indent) +             for descr in p_description.split("\n")] ) diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py new file mode 100644 index 0000000000..5ab4a892a7 --- /dev/null +++ b/indra/lib/python/indra/util/llmanifest.py @@ -0,0 +1,562 @@ +#!/usr/bin/python +"""\ +@file llmanifest.py +@author Ryan Williams +@brief Library for specifying operations on a set of files. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +import commands +import filecmp +import fnmatch +import getopt +import glob +import os +import os.path +import re +import shutil +import sys +import tarfile + +def path_ancestors(path): +    path = os.path.normpath(path) +    result = [] +    while len(path) > 0: +        result.append(path) +        path, sub = os.path.split(path) +    return result + +def proper_windows_path(path, current_platform = sys.platform): +    """ This function takes an absolute Windows or Cygwin path and +    returns a path appropriately formatted for the platform it's +    running on (as determined by sys.platform)""" +    path = path.strip() +    drive_letter = None +    rel = None +    match = re.match("/cygdrive/([a-z])/(.*)", path) +    if(not match): +        match = re.match('([a-zA-Z]):\\\(.*)', path) +    if(not match): +        return None         # not an absolute path +    drive_letter = match.group(1) +    rel = match.group(2) +    if(current_platform == "cygwin"): +        return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\\', '/') +    else: +        return drive_letter.upper() + ':\\' + rel.replace('/', '\\') + +def get_default_platform(dummy): +    return {'linux2':'linux', +            'linux1':'linux', +            'cygwin':'windows', +            'win32':'windows', +            'darwin':'darwin' +            }[sys.platform] + +def get_default_version(srctree): +    # look up llversion.h and parse out the version info +    paths = [os.path.join(srctree, x, 'llversion.h') for x in ['llcommon', '../llcommon', '../../indra/llcommon.h']] +    for p in paths: +        if os.path.exists(p): +            contents = open(p, 'r').read() +            major = re.search("LL_VERSION_MAJOR\s=\s([0-9]+)", contents).group(1) +            minor = re.search("LL_VERSION_MINOR\s=\s([0-9]+)", contents).group(1) +            patch = re.search("LL_VERSION_PATCH\s=\s([0-9]+)", contents).group(1) +            build = re.search("LL_VERSION_BUILD\s=\s([0-9]+)", contents).group(1) +            return major, minor, patch, build + +DEFAULT_CHANNEL = 'Second Life Release' + +ARGUMENTS=[ +    dict(name='actions', +         description="""This argument specifies the actions that are to be taken when the +        script is run.  The meaningful actions are currently: +          copy     - copies the files specified by the manifest into the +                     destination directory. +          package  - bundles up the files in the destination directory into +                     an installer for the current platform +          unpacked - bundles up the files in the destination directory into +                     a simple tarball +        Example use: %(name)s --actions="copy unpacked" """, +         default="copy package"), +    dict(name='arch', +         description="""This argument is appended to the platform string for +        determining which manifest class to run. +        Example use: %(name)s --arch=i686 +        On Linux this would try to use Linux_i686Manifest.""", +         default=""), +    dict(name='configuration', +         description="""The build configuration used. Only used on OS X for +        now, but it could be used for other platforms as well.""", +         default="Universal"), +    dict(name='grid', +         description="""Which grid the client will try to connect to. Even +        though it's not strictly a grid, 'firstlook' is also an acceptable +        value for this parameter.""", +         default=""), +    dict(name='channel', +         description="""The channel to use for updates.""", +         default=DEFAULT_CHANNEL), +    dict(name='installer_name', +         description=""" The name of the file that the installer should be +        packaged up into. Only used on Linux at the moment.""", +         default=None), +    dict(name='login_url', +         description="""The url that the login screen displays in the client.""", +         default=None), +    dict(name='platform', +         description="""The current platform, to be used for looking up which +        manifest class to run.""", +         default=get_default_platform), +    dict(name='version', +         description="""This specifies the version of Second Life that is +        being packaged up.""", +         default=get_default_version) +    ] + +def usage(srctree=""): +    nd = {'name':sys.argv[0]} +    print """Usage: +    %(name)s [options] [destdir] +    Options: +    """ % nd +    for arg in ARGUMENTS: +        default = arg['default'] +        if hasattr(default, '__call__'): +            default = "(computed value) \"" + str(default(srctree)) + '"' +        elif default is not None: +            default = '"' + default + '"' +        print "\t--%s        Default: %s\n\t%s\n" % ( +            arg['name'], +            default, +            arg['description'] % nd) + +def main(argv=None, srctree='.', dsttree='./dst'): +    if(argv == None): +        argv = sys.argv + +    option_names = [arg['name'] + '=' for arg in ARGUMENTS] +    option_names.append('help') +    options, remainder = getopt.getopt(argv[1:], "", option_names) +    if len(remainder) >= 1: +        dsttree = remainder[0] + +    print "Source tree:", srctree +    print "Destination tree:", dsttree + +    # convert options to a hash +    args = {} +    for opt in options: +        args[opt[0].replace("--", "")] = opt[1] + +    # early out for help +    if args.has_key('help'): +        # *TODO: it is a huge hack to pass around the srctree like this +        usage(srctree) +        return + +    # defaults +    for arg in ARGUMENTS: +        if not args.has_key(arg['name']): +            default = arg['default'] +            if hasattr(default, '__call__'): +                default = default(srctree) +            if default is not None: +                args[arg['name']] = default + +    # fix up version +    if args.has_key('version') and type(args['version']) == str: +        args['version'] = args['version'].split('.') +         +    # default and agni are default +    if args['grid'] in ['default', 'agni']: +        args['grid'] = '' + +    if args.has_key('actions'): +        args['actions'] = args['actions'].split() + +    # debugging +    for opt in args: +        print "Option:", opt, "=", args[opt] + +    wm = LLManifest.for_platform(args['platform'], args.get('arch'))(srctree, dsttree, args) +    wm.do(*args['actions']) +    return 0 + +class LLManifestRegistry(type): +    def __init__(cls, name, bases, dct): +        super(LLManifestRegistry, cls).__init__(name, bases, dct) +        match = re.match("(\w+)Manifest", name) +        if(match): +           cls.manifests[match.group(1).lower()] = cls + +class LLManifest(object): +    __metaclass__ = LLManifestRegistry +    manifests = {} +    def for_platform(self, platform, arch = None): +        if arch: +            platform = platform + '_' + arch +        return self.manifests[platform.lower()] +    for_platform = classmethod(for_platform) + +    def __init__(self, srctree, dsttree, args): +        super(LLManifest, self).__init__() +        self.args = args +        self.file_list = [] +        self.excludes = [] +        self.actions = [] +        self.src_prefix = [srctree] +        self.dst_prefix = [dsttree] +        self.created_paths = [] +         +    def default_grid(self): +        return self.args.get('grid', None) == '' +    def default_channel(self): +        return self.args.get('channel', None) == DEFAULT_CHANNEL + +    def construct(self): +        """ Meant to be overriden by LLManifest implementors with code that +        constructs the complete destination hierarchy.""" +        pass # override this method + +    def exclude(self, glob): +        """ Excludes all files that match the glob from being included +        in the file list by path().""" +        self.excludes.append(glob) + +    def prefix(self, src='', dst=None): +        """ Pushes a prefix onto the stack.  Until end_prefix is +        called, all relevant method calls (esp. to path()) will prefix +        paths with the entire prefix stack.  Source and destination +        prefixes can be different, though if only one is provided they +        are both equal.  To specify a no-op, use an empty string, not +        None.""" +        if(dst == None): +            dst = src +        self.src_prefix.append(src) +        self.dst_prefix.append(dst) +        return True  # so that you can wrap it in an if to get indentation + +    def end_prefix(self, descr=None): +        """Pops a prefix off the stack.  If given an argument, checks +        the argument against the top of the stack.  If the argument +        matches neither the source or destination prefixes at the top +        of the stack, then misnesting must have occurred and an +        exception is raised.""" +        # as an error-prevention mechanism, check the prefix and see if it matches the source or destination prefix.  If not, improper nesting may have occurred. +        src = self.src_prefix.pop() +        dst = self.dst_prefix.pop() +        if descr and not(src == descr or dst == descr): +            raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'" + +    def get_src_prefix(self): +        """ Returns the current source prefix.""" +        return os.path.join(*self.src_prefix) + +    def get_dst_prefix(self): +        """ Returns the current destination prefix.""" +        return os.path.join(*self.dst_prefix) + +    def src_path_of(self, relpath): +        """Returns the full path to a file or directory specified +        relative to the source directory.""" +        return os.path.join(self.get_src_prefix(), relpath) + +    def dst_path_of(self, relpath): +        """Returns the full path to a file or directory specified +        relative to the destination directory.""" +        return os.path.join(self.get_dst_prefix(), relpath) + +    def ensure_src_dir(self, reldir): +        """Construct the path for a directory relative to the +        source path, and ensures that it exists.  Returns the +        full path.""" +        path = os.path.join(self.get_src_prefix(), reldir) +        self.cmakedirs(path) +        return path + +    def ensure_dst_dir(self, reldir): +        """Construct the path for a directory relative to the +        destination path, and ensures that it exists.  Returns the +        full path.""" +        path = os.path.join(self.get_dst_prefix(), reldir) +        self.cmakedirs(path) +        return path + +    def run_command(self, command): +        """ Runs an external command, and returns the output.  Raises +        an exception if the command reurns a nonzero status code.  For +        debugging/informational purpoases, prints out the command's +        output as it is received.""" +        print "Running command:", command +        fd = os.popen(command, 'r') +        lines = [] +        while True: +            lines.append(fd.readline()) +            if(lines[-1] == ''): +                break +            else: +                print lines[-1], +        output = ''.join(lines) +        status = fd.close() +        if(status): +            raise RuntimeError, "Command " + command + " returned non-zero status (" + str(status) + ")" +        return output + +    def created_path(self, path): +        """ Declare that you've created a path in order to +          a) verify that you really have created it +          b) schedule it for cleanup""" +        if not os.path.exists(path): +            raise RuntimeError, "Should be something at path " + path +        self.created_paths.append(path) + +    def put_in_file(self, contents, dst): +        # write contents as dst +        f = open(self.dst_path_of(dst), "wb") +        f.write(contents) +        f.close() + +    def replace_in(self, src, dst=None, searchdict={}): +        if(dst == None): +            dst = src +        # read src +        f = open(self.src_path_of(src), "rbU") +        contents = f.read() +        f.close() +        # apply dict replacements +        for old, new in searchdict.iteritems(): +            contents = contents.replace(old, new) +        self.put_in_file(contents, dst) +        self.created_paths.append(dst) + +    def copy_action(self, src, dst): +        if(src and (os.path.exists(src) or os.path.islink(src))): +            # ensure that destination path exists +            self.cmakedirs(os.path.dirname(dst)) +            self.created_paths.append(dst) +            if(not os.path.isdir(src)): +                self.ccopy(src,dst) +            else: +                # src is a dir +                self.ccopytree(src,dst) +        else: +            print "Doesn't exist:", src + +    def package_action(self, src, dst): +        pass + +    def copy_finish(self): +        pass + +    def package_finish(self): +        pass + +    def unpacked_finish(self): +        unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % { +            'plat':self.args['platform'], +            'vers':'_'.join(self.args['version'])} +        print "Creating unpacked file:", unpacked_file_name +        # could add a gz here but that doubles the time it takes to do this step +        tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:') +        # add the entire installation package, at the very top level +        tf.add(self.get_dst_prefix(), "") +        tf.close() + +    def cleanup_finish(self): +        """ Delete paths that were specified to have been created by this script""" +        for c in self.created_paths: +            # *TODO is this gonna be useful? +            print "Cleaning up " + c + +    def process_file(self, src, dst): +        if(self.includes(src, dst)): +#            print src, "=>", dst +            for action in self.actions: +                methodname = action + "_action" +                method = getattr(self, methodname, None) +                if method is not None: +                    method(src, dst) +            self.file_list.append([src, dst]) +        else: +            print "Excluding: ", src, dst + + +    def process_directory(self, src, dst): +        if(not self.includes(src, dst)): +            print "Excluding: ", src, dst +            return +        names = os.listdir(src) +        self.cmakedirs(dst) +        errors = [] +        for name in names: +            srcname = os.path.join(src, name) +            dstname = os.path.join(dst, name) +            if os.path.isdir(srcname): +                self.process_directory(srcname, dstname) +            else: +                self.process_file(srcname, dstname) + + + +    def includes(self, src, dst): +        if src: +            for excl in self.excludes: +                if fnmatch.fnmatch(src, excl): +                    return False +        return True + +    def remove(self, *paths): +        for path in paths: +            if(os.path.exists(path)): +                print "Removing path", path +                if(os.path.isdir(path)): +                    shutil.rmtree(path) +                else: +                    os.remove(path) + +    def ccopy(self, src, dst): +        """ Copy a single file or symlink.  Uses filecmp to skip copying for existing files.""" +        if os.path.islink(src): +            linkto = os.readlink(src) +            if(os.path.islink(dst) or os.path.exists(dst)): +                os.remove(dst)  # because symlinking over an existing link fails +            os.symlink(linkto, dst) +        else: +            # Don't recopy file if it's up-to-date. +            # If we seem to be not not overwriting files that have been +            # updated, set the last arg to False, but it will take longer. +            if(os.path.exists(dst) and filecmp.cmp(src, dst, True)): +                return +            # only copy if it's not excluded +            if(self.includes(src, dst)): +                shutil.copy2(src, dst) + +    def ccopytree(self, src, dst): +        """Direct copy of shutil.copytree with the additional +        feature that the destination directory can exist.  It +        is so dumb that Python doesn't come with this. Also it +        implements the excludes functionality.""" +        if(not self.includes(src, dst)): +            return +        names = os.listdir(src) +        self.cmakedirs(dst) +        errors = [] +        for name in names: +            srcname = os.path.join(src, name) +            dstname = os.path.join(dst, name) +            try: +                if os.path.isdir(srcname): +                    self.ccopytree(srcname, dstname) +                else: +                    self.ccopy(srcname, dstname) +                    # XXX What about devices, sockets etc.? +            except (IOError, os.error), why: +                errors.append((srcname, dstname, why)) +        if errors: +            raise RuntimeError, errors + + +    def cmakedirs(self, path): +        """Ensures that a directory exists, and doesn't throw an exception +        if you call it on an existing directory.""" +#        print "making path: ", path +        path = os.path.normpath(path) +        self.created_paths.append(path) +        if not os.path.exists(path): +            os.makedirs(path) + +    def find_existing_file(self, *list): +        for f in list: +            if(os.path.exists(f)): +                return f +        # didn't find it, return last item in list +        if len(list) > 0: +            return list[-1] +        else: +            return None + +    def contents_of_tar(self, src_tar, dst_dir): +        """ Extracts the contents of the tarfile (specified +        relative to the source prefix) into the directory +        specified relative to the destination directory.""" +        self.check_file_exists(src_tar) +        tf = tarfile.open(self.src_path_of(src_tar), 'r') +        for member in tf.getmembers(): +            tf.extract(member, self.ensure_dst_dir(dst_dir)) +            # TODO get actions working on these dudes, perhaps we should extract to a temporary directory and then process_directory on it? +            self.file_list.append([src_tar, +                           self.dst_path_of(os.path.join(dst_dir,member.name))]) +        tf.close() + + +    def wildcard_regex(self, src_glob, dst_glob): + #       print "regex_pair:", src_glob, dst_glob +        src_re = re.escape(src_glob) +        src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)') +        dst_temp = dst_glob +        i = 1 +        while(dst_temp.count("*") > 0): +            dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1) +            i = i+1 + #       print "regex_result:", src_re, dst_temp +        return re.compile(src_re), dst_temp + +    def check_file_exists(self, path): +        if(not os.path.exists(path) and not os.path.islink(path)): +            raise RuntimeError("Path %s doesn't exist" % ( +                os.path.normpath(os.path.join(os.getcwd(), path)),)) + + +    wildcard_pattern = re.compile('\*') +    def expand_globs(self, src, dst): +        def fw_slash(str): +            return str.replace('\\', '/') +        def os_slash(str): +            return str.replace('/', os.path.sep) +        dst = fw_slash(dst) +        src = fw_slash(src) +        src_list = glob.glob(src) +        src_re, d_template = self.wildcard_regex(src, dst) +        for s in src_list: +            s = fw_slash(s) +            d = src_re.sub(d_template, s) +            #print "s:",s, "d_t", d_template, "dst", dst, "d", d +            yield os_slash(s), os_slash(d) + +    def path(self, src, dst=None): +        print "Processing", src, "=>", dst +        if src == None: +            raise RuntimeError("No source file, dst is " + dst) +        if dst == None: +            dst = src +        dst = os.path.join(self.get_dst_prefix(), dst) +        src = os.path.join(self.get_src_prefix(), src) + +        # expand globs +        if(self.wildcard_pattern.search(src)): +            for s,d in self.expand_globs(src, dst): +                self.process_file(s, d) +        else: +            # if we're specifying a single path (not a glob), +            # we should error out if it doesn't exist +            self.check_file_exists(src) +            # if it's a directory, recurse through it +            if(os.path.isdir(src)): +                self.process_directory(src, dst) +            else: +                self.process_file(src, dst) + + +    def do(self, *actions): +        self.actions = actions +        self.construct() +        # perform finish actions +        for action in self.actions: +            methodname = action + "_finish" +            method = getattr(self, methodname, None) +            if method is not None: +                method() +        return self.file_list | 
