aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/lib
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/lib')
-rw-r--r--linden/indra/lib/python/indra/base/cllsd_test.py51
-rw-r--r--linden/indra/lib/python/indra/base/config.py236
-rw-r--r--linden/indra/lib/python/indra/base/llsd.py110
-rw-r--r--linden/indra/lib/python/indra/base/lluuid.py32
-rw-r--r--linden/indra/lib/python/indra/ipc/llsdhttp.py13
-rw-r--r--linden/indra/lib/python/indra/ipc/mysql_pool.py100
-rw-r--r--linden/indra/lib/python/indra/ipc/siesta.py402
-rw-r--r--linden/indra/lib/python/indra/ipc/siesta_test.py214
-rw-r--r--linden/indra/lib/python/indra/util/fastest_elementtree.py52
-rw-r--r--linden/indra/lib/python/indra/util/llmanifest.py217
-rw-r--r--linden/indra/lib/python/indra/util/named_query.py7
-rw-r--r--linden/indra/lib/python/indra/util/term.py222
12 files changed, 1399 insertions, 257 deletions
diff --git a/linden/indra/lib/python/indra/base/cllsd_test.py b/linden/indra/lib/python/indra/base/cllsd_test.py
new file mode 100644
index 0000000..3af59e7
--- /dev/null
+++ b/linden/indra/lib/python/indra/base/cllsd_test.py
@@ -0,0 +1,51 @@
1from indra.base import llsd, lluuid
2from datetime import datetime
3import cllsd
4import time, sys
5
6class myint(int):
7 pass
8
9values = (
10 '&<>',
11 u'\u81acj',
12 llsd.uri('http://foo<'),
13 lluuid.LLUUID(),
14 llsd.LLSD(['thing']),
15 1,
16 myint(31337),
17 sys.maxint + 10,
18 llsd.binary('foo'),
19 [],
20 {},
21 {u'f&\u1212': 3},
22 3.1,
23 True,
24 None,
25 datetime.fromtimestamp(time.time()),
26 )
27
28def valuator(values):
29 for v in values:
30 yield v
31
32longvalues = () # (values, list(values), iter(values), valuator(values))
33
34for v in values + longvalues:
35 print '%r => %r' % (v, cllsd.llsd_to_xml(v))
36
37a = [[{'a':3}]] * 1000000
38
39s = time.time()
40print hash(cllsd.llsd_to_xml(a))
41e = time.time()
42t1 = e - s
43print t1
44
45s = time.time()
46print hash(llsd.LLSDXMLFormatter()._format(a))
47e = time.time()
48t2 = e - s
49print t2
50
51print 'Speedup:', t2 / t1
diff --git a/linden/indra/lib/python/indra/base/config.py b/linden/indra/lib/python/indra/base/config.py
index 3865f5e..9c37ecf 100644
--- a/linden/indra/lib/python/indra/base/config.py
+++ b/linden/indra/lib/python/indra/base/config.py
@@ -26,74 +26,224 @@ THE SOFTWARE.
26$/LicenseInfo$ 26$/LicenseInfo$
27""" 27"""
28 28
29from os.path import dirname, join, realpath 29import copy
30import os
31import traceback
32import time
30import types 33import types
34
35from os.path import dirname, getmtime, join, realpath
31from indra.base import llsd 36from indra.base import llsd
32 37
33_g_config_dict = None 38_g_config = None
34 39
35def load(indra_xml_file=None): 40class IndraConfig(object):
36 global _g_config_dict 41 """
37 if _g_config_dict == None: 42 IndraConfig loads a 'indra' xml configuration file
38 if indra_xml_file is None: 43 and loads into memory. This representation in memory
39 ## going from: 44 can get updated to overwrite values or add new values.
40 ## "/opt/linden/indra/lib/python/indra/base/config.py" 45
41 ## to: 46 The xml configuration file is considered a live file and changes
42 ## "/opt/linden/etc/indra.xml" 47 to the file are checked and reloaded periodically. If a value had
43 indra_xml_file = realpath( 48 been overwritten via the update or set method, the loaded values
44 dirname(realpath(__file__)) + "../../../../../../etc/indra.xml") 49 from the file are ignored (the values from the update/set methods
45 config_file = file(indra_xml_file) 50 override)
46 _g_config_dict = llsd.LLSD().parse(config_file.read()) 51 """
52 def __init__(self, indra_config_file):
53 self._indra_config_file = indra_config_file
54 self._reload_check_interval = 30 # seconds
55 self._last_check_time = 0
56 self._last_mod_time = 0
57
58 self._config_overrides = {}
59 self._config_file_dict = {}
60 self._combined_dict = {}
61
62 self._load()
63
64 def _load(self):
65 if self._indra_config_file is None:
66 return
67
68 config_file = open(self._indra_config_file)
69 self._config_file_dict = llsd.parse(config_file.read())
70 self._combine_dictionaries()
47 config_file.close() 71 config_file.close()
48 #print "loaded config from",indra_xml_file,"into",_g_config_dict
49 72
50def dump(indra_xml_file, indra_cfg={}, update_in_mem=False): 73 self._last_mod_time = self._get_last_modified_time()
74 self._last_check_time = time.time() # now
75
76 def _get_last_modified_time(self):
77 """
78 Returns the mtime (last modified time) of the config file,
79 if such exists.
80 """
81 if self._indra_config_file is not None:
82 return os.path.getmtime(self._indra_config_file)
83
84 return 0
85
86 def _combine_dictionaries(self):
87 self._combined_dict = {}
88 self._combined_dict.update(self._config_file_dict)
89 self._combined_dict.update(self._config_overrides)
90
91 def _reload_if_necessary(self):
92 now = time.time()
93
94 if (now - self._last_check_time) > self._reload_check_interval:
95 self._last_check_time = now
96 try:
97 modtime = self._get_last_modified_time()
98 if modtime > self._last_mod_time:
99 self._load()
100 except OSError, e:
101 if e.errno == errno.ENOENT: # file not found
102 # someone messed with our internal state
103 # or removed the file
104
105 print 'WARNING: Configuration file has been removed ' + (self._indra_config_file)
106 print 'Disabling reloading of configuration file.'
107
108 traceback.print_exc()
109
110 self._indra_config_file = None
111 self._last_check_time = 0
112 self._last_mod_time = 0
113 else:
114 raise # pass the exception along to the caller
115
116 def __getitem__(self, key):
117 self._reload_if_necessary()
118
119 return self._combined_dict[key]
120
121 def get(self, key, default = None):
122 try:
123 return self.__getitem__(key)
124 except KeyError:
125 return default
126
127 def __setitem__(self, key, value):
128 """
129 Sets the value of the config setting of key to be newval
130
131 Once any key/value pair is changed via the set method,
132 that key/value pair will remain set with that value until
133 change via the update or set method
134 """
135 self._config_overrides[key] = value
136 self._combine_dictionaries()
137
138 def set(self, key, newval):
139 return self.__setitem__(key, newval)
140
141 def update(self, new_conf):
142 """
143 Load an XML file and apply its map as overrides or additions
144 to the existing config. Update can be a file or a dict.
145
146 Once any key/value pair is changed via the update method,
147 that key/value pair will remain set with that value until
148 change via the update or set method
149 """
150 if isinstance(new_conf, dict):
151 overrides = new_conf
152 else:
153 # assuming that it is a filename
154 config_file = open(new_conf)
155 overrides = llsd.parse(config_file.read())
156 config_file.close()
157
158 self._config_overrides.update(overrides)
159 self._combine_dictionaries()
160
161 def as_dict(self):
162 """
163 Returns immutable copy of the IndraConfig as a dictionary
164 """
165 return copy.deepcopy(self._combined_dict)
166
167def load(indra_xml_file = None):
168 global _g_config
169
170 if indra_xml_file is None:
171 ## going from:
172 ## "/opt/linden/indra/lib/python/indra/base/config.py"
173 ## to:
174 ## "/opt/linden/etc/indra.xml"
175 indra_xml_file = realpath(
176 dirname(realpath(__file__)) + "../../../../../../etc/indra.xml")
177
178 try:
179 _g_config = IndraConfig(indra_xml_file)
180 except IOError:
181 # indra.xml was not openable, so let's initialize with an empty dict
182 # some code relies on config behaving this way
183 _g_config = IndraConfig(None)
184
185def dump(indra_xml_file, indra_cfg = None, update_in_mem=False):
51 ''' 186 '''
52 Dump config contents into a file 187 Dump config contents into a file
53 Kindof reverse of load. 188 Kindof reverse of load.
54 Optionally takes a new config to dump. 189 Optionally takes a new config to dump.
55 Does NOT update global config unless requested. 190 Does NOT update global config unless requested.
56 ''' 191 '''
57 global _g_config_dict 192 global _g_config
193
58 if not indra_cfg: 194 if not indra_cfg:
59 indra_cfg = _g_config_dict 195 if _g_config is None:
196 return
197
198 indra_cfg = _g_config.as_dict()
199
60 if not indra_cfg: 200 if not indra_cfg:
61 return 201 return
202
62 config_file = open(indra_xml_file, 'w') 203 config_file = open(indra_xml_file, 'w')
63 _config_xml = llsd.format_xml(indra_cfg) 204 _config_xml = llsd.format_xml(indra_cfg)
64 config_file.write(_config_xml) 205 config_file.write(_config_xml)
65 config_file.close() 206 config_file.close()
207
66 if update_in_mem: 208 if update_in_mem:
67 update(indra_cfg) 209 update(indra_cfg)
68 210
69def update(new_conf): 211def update(new_conf):
70 """Load an XML file and apply its map as overrides or additions 212 global _g_config
71 to the existing config. The dataserver does this with indra.xml 213
72 and dataserver.xml.""" 214 if _g_config is None:
73 global _g_config_dict 215 # To keep with how this function behaved
74 if _g_config_dict == None: 216 # previously, a call to update
75 _g_config_dict = {} 217 # before the global is defined
76 if isinstance(new_conf, dict): 218 # make a new global config which does not
77 overrides = new_conf 219 # load data from a file.
78 else: 220 _g_config = IndraConfig(None)
79 config_file = file(new_conf) 221
80 overrides = llsd.LLSD().parse(config_file.read()) 222 return _g_config.update(new_conf)
81 config_file.close()
82
83 _g_config_dict.update(overrides)
84 223
85def get(key, default = None): 224def get(key, default = None):
86 global _g_config_dict 225 global _g_config
87 if _g_config_dict == None: 226
227 if _g_config is None:
88 load() 228 load()
89 return _g_config_dict.get(key, default) 229
230 return _g_config.get(key, default)
90 231
91def set(key, newval): 232def set(key, newval):
92 global _g_config_dict 233 """
93 if _g_config_dict == None: 234 Sets the value of the config setting of key to be newval
94 load() 235
95 _g_config_dict[key] = newval 236 Once any key/value pair is changed via the set method,
237 that key/value pair will remain set with that value until
238 change via the update or set method or program termination
239 """
240 global _g_config
241
242 if _g_config is None:
243 _g_config = IndraConfig(None)
244
245 _g_config.set(key, newval)
96 246
97def as_dict(): 247def get_config():
98 global _g_config_dict 248 global _g_config
99 return _g_config_dict 249 return _g_config
diff --git a/linden/indra/lib/python/indra/base/llsd.py b/linden/indra/lib/python/indra/base/llsd.py
index cd23551..995ace7 100644
--- a/linden/indra/lib/python/indra/base/llsd.py
+++ b/linden/indra/lib/python/indra/base/llsd.py
@@ -33,14 +33,7 @@ import time
33import types 33import types
34import re 34import re
35 35
36#from cElementTree import fromstring ## This does not work under Windows 36from indra.util.fastest_elementtree import fromstring
37try:
38 ## This is the old name of elementtree, for use with 2.3
39 from elementtree.ElementTree import fromstring
40except ImportError:
41 ## This is the name of elementtree under python 2.5
42 from xml.etree.ElementTree import fromstring
43
44from indra.base import lluuid 37from indra.base import lluuid
45 38
46int_regex = re.compile("[-+]?\d+") 39int_regex = re.compile("[-+]?\d+")
@@ -67,6 +60,39 @@ BOOL_TRUE = ('1', '1.0', 'true')
67BOOL_FALSE = ('0', '0.0', 'false', '') 60BOOL_FALSE = ('0', '0.0', 'false', '')
68 61
69 62
63def format_datestr(v):
64 """ Formats a datetime object into the string format shared by xml and notation serializations."""
65 second_str = ""
66 if v.microsecond > 0:
67 seconds = v.second + float(v.microsecond) / 1000000
68 second_str = "%05.2f" % seconds
69 else:
70 second_str = "%d" % v.second
71 return '%s%sZ' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str)
72
73
74def parse_datestr(datestr):
75 """Parses a datetime object from the string format shared by xml and notation serializations."""
76 if datestr == "":
77 return datetime.datetime(1970, 1, 1)
78
79 match = re.match(date_regex, datestr)
80 if not match:
81 raise LLSDParseError("invalid date string '%s'." % datestr)
82
83 year = int(match.group('year'))
84 month = int(match.group('month'))
85 day = int(match.group('day'))
86 hour = int(match.group('hour'))
87 minute = int(match.group('minute'))
88 second = int(match.group('second'))
89 seconds_float = match.group('second_float')
90 microsecond = 0
91 if seconds_float:
92 microsecond = int(seconds_float[1:]) * 10000
93 return datetime.datetime(year, month, day, hour, minute, second, microsecond)
94
95
70def bool_to_python(node): 96def bool_to_python(node):
71 val = node.text or '' 97 val = node.text or ''
72 if val in BOOL_TRUE: 98 if val in BOOL_TRUE:
@@ -99,8 +125,7 @@ def date_to_python(node):
99 val = node.text or '' 125 val = node.text or ''
100 if not val: 126 if not val:
101 val = "1970-01-01T00:00:00Z" 127 val = "1970-01-01T00:00:00Z"
102 return datetime.datetime( 128 return parse_datestr(val)
103 *time.strptime(val, '%Y-%m-%dT%H:%M:%SZ')[:6])
104 129
105def uri_to_python(node): 130def uri_to_python(node):
106 val = node.text or '' 131 val = node.text or ''
@@ -194,7 +219,7 @@ class LLSDXMLFormatter(object):
194 def URI(self, v): 219 def URI(self, v):
195 return self.elt('uri', self.xml_esc(str(v))) 220 return self.elt('uri', self.xml_esc(str(v)))
196 def DATE(self, v): 221 def DATE(self, v):
197 return self.elt('date', v.strftime('%Y-%m-%dT%H:%M:%SZ')) 222 return self.elt('date', format_datestr(v))
198 def ARRAY(self, v): 223 def ARRAY(self, v):
199 return self.elt('array', ''.join([self.generate(item) for item in v])) 224 return self.elt('array', ''.join([self.generate(item) for item in v]))
200 def MAP(self, v): 225 def MAP(self, v):
@@ -215,8 +240,12 @@ class LLSDXMLFormatter(object):
215 def format(self, something): 240 def format(self, something):
216 return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) 241 return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something))
217 242
243_g_xml_formatter = None
218def format_xml(something): 244def format_xml(something):
219 return LLSDXMLFormatter().format(something) 245 global _g_xml_formatter
246 if _g_xml_formatter is None:
247 _g_xml_formatter = LLSDXMLFormatter()
248 return _g_xml_formatter.format(something)
220 249
221class LLSDXMLPrettyFormatter(LLSDXMLFormatter): 250class LLSDXMLPrettyFormatter(LLSDXMLFormatter):
222 def __init__(self, indent_atom = None): 251 def __init__(self, indent_atom = None):
@@ -333,13 +362,7 @@ class LLSDNotationFormatter(object):
333 def URI(self, v): 362 def URI(self, v):
334 return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') 363 return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"')
335 def DATE(self, v): 364 def DATE(self, v):
336 second_str = "" 365 return 'd"%s"' % format_datestr(v)
337 if v.microsecond > 0:
338 seconds = v.second + float(v.microsecond) / 1000000
339 second_str = "%05.2f" % seconds
340 else:
341 second_str = "%d" % v.second
342 return 'd"%s%sZ"' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str)
343 def ARRAY(self, v): 366 def ARRAY(self, v):
344 return "[%s]" % ','.join([self.generate(item) for item in v]) 367 return "[%s]" % ','.join([self.generate(item) for item in v])
345 def MAP(self, v): 368 def MAP(self, v):
@@ -548,10 +571,11 @@ class LLSDNotationParser(object):
548 integer: i#### 571 integer: i####
549 real: r#### 572 real: r####
550 uuid: u#### 573 uuid: u####
551 string: "g'day" | 'have a "nice" day' | s(size)"raw data" 574 string: "g\'day" | 'have a "nice" day' | s(size)"raw data"
552 uri: l"escaped" 575 uri: l"escaped"
553 date: d"YYYY-MM-DDTHH:MM:SS.FFZ" 576 date: d"YYYY-MM-DDTHH:MM:SS.FFZ"
554 binary: b##"ff3120ab1" | b(size)"raw data" """ 577 binary: b##"ff3120ab1" | b(size)"raw data"
578 """
555 def __init__(self): 579 def __init__(self):
556 pass 580 pass
557 581
@@ -614,7 +638,6 @@ class LLSDNotationParser(object):
614 elif cc == 'b': 638 elif cc == 'b':
615 raise LLSDParseError("binary notation not yet supported") 639 raise LLSDParseError("binary notation not yet supported")
616 else: 640 else:
617 print cc
618 raise LLSDParseError("invalid token at index %d: %d" % ( 641 raise LLSDParseError("invalid token at index %d: %d" % (
619 self._index - 1, ord(cc))) 642 self._index - 1, ord(cc)))
620 643
@@ -695,25 +718,7 @@ class LLSDNotationParser(object):
695 delim = self._buffer[self._index] 718 delim = self._buffer[self._index]
696 self._index += 1 719 self._index += 1
697 datestr = self._parse_string(delim) 720 datestr = self._parse_string(delim)
698 721 return parse_datestr(datestr)
699 if datestr == "":
700 return datetime.datetime(1970, 1, 1)
701
702 match = re.match(date_regex, datestr)
703 if not match:
704 raise LLSDParseError("invalid date string '%s'." % datestr)
705
706 year = int(match.group('year'))
707 month = int(match.group('month'))
708 day = int(match.group('day'))
709 hour = int(match.group('hour'))
710 minute = int(match.group('minute'))
711 second = int(match.group('second'))
712 seconds_float = match.group('second_float')
713 microsecond = 0
714 if seconds_float:
715 microsecond = int(seconds_float[1:]) * 10000
716 return datetime.datetime(year, month, day, hour, minute, second, microsecond)
717 722
718 def _parse_real(self): 723 def _parse_real(self):
719 match = re.match(real_regex, self._buffer[self._index:]) 724 match = re.match(real_regex, self._buffer[self._index:])
@@ -738,7 +743,7 @@ class LLSDNotationParser(object):
738 return int( self._buffer[start:end] ) 743 return int( self._buffer[start:end] )
739 744
740 def _parse_string(self, delim): 745 def _parse_string(self, delim):
741 """ string: "g'day" | 'have a "nice" day' | s(size)"raw data" """ 746 """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """
742 rv = "" 747 rv = ""
743 748
744 if delim in ("'", '"'): 749 if delim in ("'", '"'):
@@ -908,22 +913,17 @@ class LLSD(object):
908 913
909undef = LLSD(None) 914undef = LLSD(None)
910 915
911# register converters for stacked, if stacked is available 916# register converters for llsd in mulib, if it is available
912try: 917try:
913 from mulib import stacked 918 from mulib import stacked, mu
914 stacked.NoProducer() # just to exercise stacked 919 stacked.NoProducer() # just to exercise stacked
920 mu.safe_load(None) # just to exercise mu
915except: 921except:
916 print "Couldn't import mulib.stacked, not registering LLSD converters" 922 # mulib not available, don't print an error message since this is normal
923 pass
917else: 924else:
918 def llsd_convert_json(llsd_stuff, request): 925 mu.add_parser(parse, 'application/llsd+xml')
919 callback = request.get_header('callback') 926 mu.add_parser(parse, 'application/llsd+binary')
920 if callback is not None:
921 ## See Yahoo's ajax documentation for information about using this
922 ## callback style of programming
923 ## http://developer.yahoo.com/common/json.html#callbackparam
924 req.write("%s(%s)" % (callback, simplejson.dumps(llsd_stuff)))
925 else:
926 req.write(simplejson.dumps(llsd_stuff))
927 927
928 def llsd_convert_xml(llsd_stuff, request): 928 def llsd_convert_xml(llsd_stuff, request):
929 request.write(format_xml(llsd_stuff)) 929 request.write(format_xml(llsd_stuff))
@@ -932,8 +932,6 @@ else:
932 request.write(format_binary(llsd_stuff)) 932 request.write(format_binary(llsd_stuff))
933 933
934 for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: 934 for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]:
935 stacked.add_producer(typ, llsd_convert_json, 'application/json')
936
937 stacked.add_producer(typ, llsd_convert_xml, 'application/llsd+xml') 935 stacked.add_producer(typ, llsd_convert_xml, 'application/llsd+xml')
938 stacked.add_producer(typ, llsd_convert_xml, 'application/xml') 936 stacked.add_producer(typ, llsd_convert_xml, 'application/xml')
939 stacked.add_producer(typ, llsd_convert_xml, 'text/xml') 937 stacked.add_producer(typ, llsd_convert_xml, 'text/xml')
diff --git a/linden/indra/lib/python/indra/base/lluuid.py b/linden/indra/lib/python/indra/base/lluuid.py
index dd336f0..0756889 100644
--- a/linden/indra/lib/python/indra/base/lluuid.py
+++ b/linden/indra/lib/python/indra/base/lluuid.py
@@ -74,21 +74,29 @@ class UUID(object):
74 hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) 74 hexip = ''.join(["%04x" % long(i) for i in ip.split('.')])
75 lastid = '' 75 lastid = ''
76 76
77 def __init__(self, string_with_uuid=None): 77 def __init__(self, possible_uuid=None):
78 """ 78 """
79 Initialize to first valid UUID in string argument, 79 Initialize to first valid UUID in argument (if a string),
80 or to null UUID if none found or string is not supplied. 80 or to null UUID if none found or argument is not supplied.
81
82 If the argument is a UUID, the constructed object will be a copy of it.
81 """ 83 """
82 self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 84 self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
83 if string_with_uuid: 85 if possible_uuid is None:
84 uuid_match = UUID.uuid_regex.search(string_with_uuid) 86 return
85 if uuid_match: 87
86 uuid_string = uuid_match.group() 88 if isinstance(possible_uuid, type(self)):
87 s = string.replace(uuid_string, '-', '') 89 self.set(possible_uuid)
88 self._bits = _int2binstr(string.atol(s[:8],16),4) + \ 90 return
89 _int2binstr(string.atol(s[8:16],16),4) + \ 91
90 _int2binstr(string.atol(s[16:24],16),4) + \ 92 uuid_match = UUID.uuid_regex.search(possible_uuid)
91 _int2binstr(string.atol(s[24:],16),4) 93 if uuid_match:
94 uuid_string = uuid_match.group()
95 s = string.replace(uuid_string, '-', '')
96 self._bits = _int2binstr(string.atol(s[:8],16),4) + \
97 _int2binstr(string.atol(s[8:16],16),4) + \
98 _int2binstr(string.atol(s[16:24],16),4) + \
99 _int2binstr(string.atol(s[24:],16),4)
92 100
93 def __len__(self): 101 def __len__(self):
94 """ 102 """
diff --git a/linden/indra/lib/python/indra/ipc/llsdhttp.py b/linden/indra/lib/python/indra/ipc/llsdhttp.py
index 24cad61..1cf1146 100644
--- a/linden/indra/lib/python/indra/ipc/llsdhttp.py
+++ b/linden/indra/lib/python/indra/ipc/llsdhttp.py
@@ -48,7 +48,11 @@ put_ = suite.put_
48request = suite.request 48request = suite.request
49request_ = suite.request_ 49request_ = suite.request_
50 50
51for x in (httpc.ConnectionError, httpc.NotFound, httpc.Forbidden): 51# import every httpc error exception into our namespace for convenience
52for x in httpc.status_to_error_map.itervalues():
53 globals()[x.__name__] = x
54
55for x in (httpc.ConnectionError,):
52 globals()[x.__name__] = x 56 globals()[x.__name__] = x
53 57
54 58
@@ -60,21 +64,22 @@ def postFile(url, filename):
60 return post_(url, llsd_body) 64 return post_(url, llsd_body)
61 65
62 66
67# deprecated in favor of get_
63def getStatus(url, use_proxy=False): 68def getStatus(url, use_proxy=False):
64 status, _headers, _body = get_(url, use_proxy=use_proxy) 69 status, _headers, _body = get_(url, use_proxy=use_proxy)
65 return status 70 return status
66 71
67 72# deprecated in favor of put_
68def putStatus(url, data): 73def putStatus(url, data):
69 status, _headers, _body = put_(url, data) 74 status, _headers, _body = put_(url, data)
70 return status 75 return status
71 76
72 77# deprecated in favor of delete_
73def deleteStatus(url): 78def deleteStatus(url):
74 status, _headers, _body = delete_(url) 79 status, _headers, _body = delete_(url)
75 return status 80 return status
76 81
77 82# deprecated in favor of post_
78def postStatus(url, data): 83def postStatus(url, data):
79 status, _headers, _body = post_(url, data) 84 status, _headers, _body = post_(url, data)
80 return status 85 return status
diff --git a/linden/indra/lib/python/indra/ipc/mysql_pool.py b/linden/indra/lib/python/indra/ipc/mysql_pool.py
index 01e31bb..507b185 100644
--- a/linden/indra/lib/python/indra/ipc/mysql_pool.py
+++ b/linden/indra/lib/python/indra/ipc/mysql_pool.py
@@ -1,6 +1,6 @@
1"""\ 1"""\
2@file mysql_pool.py 2@file mysql_pool.py
3@brief Uses saranwrap to implement a pool of nonblocking database connections to a mysql server. 3@brief Thin wrapper around eventlet.db_pool that chooses MySQLdb and Tpool.
4 4
5$LicenseInfo:firstyear=2007&license=mit$ 5$LicenseInfo:firstyear=2007&license=mit$
6 6
@@ -26,44 +26,14 @@ THE SOFTWARE.
26$/LicenseInfo$ 26$/LicenseInfo$
27""" 27"""
28 28
29import os
30
31from eventlet.pools import Pool
32from eventlet.processes import DeadProcess
33from indra.ipc import saranwrap
34
35import MySQLdb 29import MySQLdb
30from eventlet import db_pool
36 31
37# method 2: better -- admits the existence of the pool 32class DatabaseConnector(db_pool.DatabaseConnector):
38# dbp = my_db_connector.get()
39# dbh = dbp.get()
40# dbc = dbh.cursor()
41# dbc.execute(named_query)
42# dbc.close()
43# dbp.put(dbh)
44
45class DatabaseConnector(object):
46 """\
47@brief This is an object which will maintain a collection of database
48connection pools keyed on host,databasename"""
49 def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs): 33 def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs):
50 """\ 34 super(DatabaseConnector, self).__init__(MySQLdb, credentials, min_size, max_size, conn_pool=db_pool.ConnectionPool, *args, **kwargs)
51 @brief constructor
52 @param min_size the minimum size of a child pool.
53 @param max_size the maximum size of a child pool."""
54 self._min_size = min_size
55 self._max_size = max_size
56 self._args = args
57 self._kwargs = kwargs
58 self._credentials = credentials # this is a map of hostname to username/password
59 self._databases = {}
60
61 def credentials_for(self, host):
62 if host in self._credentials:
63 return self._credentials[host]
64 else:
65 return self._credentials.get('default', None)
66 35
36 # get is extended relative to eventlet.db_pool to accept a port argument
67 def get(self, host, dbname, port=3306): 37 def get(self, host, dbname, port=3306):
68 key = (host, dbname, port) 38 key = (host, dbname, port)
69 if key not in self._databases: 39 if key not in self._databases:
@@ -77,28 +47,44 @@ connection pools keyed on host,databasename"""
77 47
78 return self._databases[key] 48 return self._databases[key]
79 49
80 50class ConnectionPool(db_pool.TpooledConnectionPool):
81class ConnectionPool(Pool):
82 """A pool which gives out saranwrapped MySQLdb connections from a pool 51 """A pool which gives out saranwrapped MySQLdb connections from a pool
83 """ 52 """
84 def __init__(self, min_size = 0, max_size = 4, *args, **kwargs):
85 self._args = args
86 self._kwargs = kwargs
87 Pool.__init__(self, min_size, max_size)
88 53
89 def create(self): 54 def __init__(self, min_size = 0, max_size = 4, *args, **kwargs):
90 return saranwrap.wrap(MySQLdb).connect(*self._args, **self._kwargs) 55 super(ConnectionPool, self).__init__(MySQLdb, min_size, max_size, *args, **kwargs)
91 56
92 def put(self, conn): 57 def get(self):
93 # rollback any uncommitted changes, so that the next process 58 conn = super(ConnectionPool, self).get()
94 # has a clean slate. This also pokes the process to see if 59 # annotate the connection object with the details on the
95 # it's dead or None 60 # connection; this is used elsewhere to check that you haven't
96 try: 61 # suddenly changed databases in midstream while making a
97 conn.rollback() 62 # series of queries on a connection.
98 except (AttributeError, DeadProcess), e: 63 arg_names = ['host','user','passwd','db','port','unix_socket','conv','connect_timeout',
99 conn = self.create() 64 'compress', 'named_pipe', 'init_command', 'read_default_file', 'read_default_group',
100 # TODO figure out if we're still connected to the database 65 'cursorclass', 'use_unicode', 'charset', 'sql_mode', 'client_flag', 'ssl',
101 if conn is not None: 66 'local_infile']
102 Pool.put(self, conn) 67 # you could have constructed this connectionpool with a mix of
103 else: 68 # keyword and non-keyword arguments, but we want to annotate
104 self.current_size -= 1 69 # the connection object with a dict so it's easy to check
70 # against so here we are converting the list of non-keyword
71 # arguments (in self._args) into a dict of keyword arguments,
72 # and merging that with the actual keyword arguments
73 # (self._kwargs). The arg_names variable lists the
74 # constructor arguments for MySQLdb Connection objects.
75 converted_kwargs = dict([ (arg_names[i], arg) for i, arg in enumerate(self._args) ])
76 converted_kwargs.update(self._kwargs)
77 conn.connection_parameters = converted_kwargs
78 return conn
79
80 def clear(self):
81 """ Close all connections that this pool still holds a reference to, leaving it empty."""
82 for conn in self.free_items:
83 try:
84 conn.close()
85 except:
86 pass # even if stuff happens here, we still want to at least try to close all the other connections
87 self.free_items.clear()
88
89 def __del__(self):
90 self.clear()
diff --git a/linden/indra/lib/python/indra/ipc/siesta.py b/linden/indra/lib/python/indra/ipc/siesta.py
new file mode 100644
index 0000000..5fbea29
--- /dev/null
+++ b/linden/indra/lib/python/indra/ipc/siesta.py
@@ -0,0 +1,402 @@
1from indra.base import llsd
2from webob import exc
3import webob
4import re, socket
5
6try:
7 from cStringIO import StringIO
8except ImportError:
9 from StringIO import StringIO
10
11try:
12 import cjson
13 json_decode = cjson.decode
14 json_encode = cjson.encode
15 JsonDecodeError = cjson.DecodeError
16 JsonEncodeError = cjson.EncodeError
17except ImportError:
18 import simplejson
19 json_decode = simplejson.loads
20 json_encode = simplejson.dumps
21 JsonDecodeError = ValueError
22 JsonEncodeError = TypeError
23
24
25llsd_parsers = {
26 'application/json': json_decode,
27 'application/llsd+binary': llsd.parse_binary,
28 'application/llsd+notation': llsd.parse_notation,
29 'application/llsd+xml': llsd.parse_xml,
30 'application/xml': llsd.parse_xml,
31 }
32
33
34def mime_type(content_type):
35 '''Given a Content-Type header, return only the MIME type.'''
36
37 return content_type.split(';', 1)[0].strip().lower()
38
39class BodyLLSD(object):
40 '''Give a webob Request or Response an llsd property.
41
42 Getting the llsd property parses the body, and caches the result.
43
44 Setting the llsd property formats a payload, and the body property
45 is set.'''
46
47 def _llsd__get(self):
48 '''Get, set, or delete the LLSD value stored in this object.'''
49
50 try:
51 return self._llsd
52 except AttributeError:
53 if not self.body:
54 raise AttributeError('No llsd attribute has been set')
55 else:
56 mtype = mime_type(self.content_type)
57 try:
58 parser = llsd_parsers[mtype]
59 except KeyError:
60 raise exc.HTTPUnsupportedMediaType(
61 'Content type %s not supported' % mtype).exception
62 try:
63 self._llsd = parser(self.body)
64 except (llsd.LLSDParseError, JsonDecodeError, TypeError), err:
65 raise exc.HTTPBadRequest(
66 'Could not parse body: %r' % err.args).exception
67 return self._llsd
68
69 def _llsd__set(self, val):
70 req = getattr(self, 'request', None)
71 if req is not None:
72 formatter, ctype = formatter_for_request(req)
73 self.content_type = ctype
74 else:
75 formatter, ctype = formatter_for_mime_type(
76 mime_type(self.content_type))
77 self.body = formatter(val)
78
79 def _llsd__del(self):
80 if hasattr(self, '_llsd'):
81 del self._llsd
82
83 llsd = property(_llsd__get, _llsd__set, _llsd__del)
84
85
86class Response(webob.Response, BodyLLSD):
87 '''Response class with LLSD support.
88
89 A sensible default content type is used.
90
91 Setting the llsd property also sets the body. Getting the llsd
92 property parses the body if necessary.
93
94 If you set the body property directly, the llsd property will be
95 deleted.'''
96
97 default_content_type = 'application/llsd+xml'
98
99 def _body__set(self, body):
100 if hasattr(self, '_llsd'):
101 del self._llsd
102 super(Response, self)._body__set(body)
103
104 def cache_forever(self):
105 self.cache_expires(86400 * 365)
106
107 body = property(webob.Response._body__get, _body__set,
108 webob.Response._body__del,
109 webob.Response._body__get.__doc__)
110
111
112class Request(webob.Request, BodyLLSD):
113 '''Request class with LLSD support.
114
115 Sensible content type and accept headers are used by default.
116
117 Setting the llsd property also sets the body. Getting the llsd
118 property parses the body if necessary.
119
120 If you set the body property directly, the llsd property will be
121 deleted.'''
122
123 default_content_type = 'application/llsd+xml'
124 default_accept = ('application/llsd+xml; q=0.5, '
125 'application/llsd+notation; q=0.3, '
126 'application/llsd+binary; q=0.2, '
127 'application/xml; q=0.1, '
128 'application/json; q=0.0')
129
130 def __init__(self, environ=None, *args, **kwargs):
131 if environ is None:
132 environ = {}
133 else:
134 environ = environ.copy()
135 if 'CONTENT_TYPE' not in environ:
136 environ['CONTENT_TYPE'] = self.default_content_type
137 if 'HTTP_ACCEPT' not in environ:
138 environ['HTTP_ACCEPT'] = self.default_accept
139 super(Request, self).__init__(environ, *args, **kwargs)
140
141 def _body__set(self, body):
142 if hasattr(self, '_llsd'):
143 del self._llsd
144 super(Request, self)._body__set(body)
145
146 def path_urljoin(self, *parts):
147 return '/'.join([path_url.rstrip('/')] + list(parts))
148
149 body = property(webob.Request._body__get, _body__set,
150 webob.Request._body__del, webob.Request._body__get.__doc__)
151
152 def create_response(self, llsd=None, status='200 OK',
153 conditional_response=webob.NoDefault):
154 resp = self.ResponseClass(status=status, request=self,
155 conditional_response=conditional_response)
156 resp.llsd = llsd
157 return resp
158
159 def curl(self):
160 '''Create and fill out a pycurl easy object from this request.'''
161
162 import pycurl
163 c = pycurl.Curl()
164 c.setopt(pycurl.URL, self.url())
165 if self.headers:
166 c.setopt(pycurl.HTTPHEADER,
167 ['%s: %s' % (k, self.headers[k]) for k in self.headers])
168 c.setopt(pycurl.FOLLOWLOCATION, True)
169 c.setopt(pycurl.AUTOREFERER, True)
170 c.setopt(pycurl.MAXREDIRS, 16)
171 c.setopt(pycurl.NOSIGNAL, True)
172 c.setopt(pycurl.READFUNCTION, self.body_file.read)
173 c.setopt(pycurl.SSL_VERIFYHOST, 2)
174
175 if self.method == 'POST':
176 c.setopt(pycurl.POST, True)
177 post301 = getattr(pycurl, 'POST301', None)
178 if post301 is not None:
179 # Added in libcurl 7.17.1.
180 c.setopt(post301, True)
181 elif self.method == 'PUT':
182 c.setopt(pycurl.PUT, True)
183 elif self.method != 'GET':
184 c.setopt(pycurl.CUSTOMREQUEST, self.method)
185 return c
186
187Request.ResponseClass = Response
188Response.RequestClass = Request
189
190
191llsd_formatters = {
192 'application/json': json_encode,
193 'application/llsd+binary': llsd.format_binary,
194 'application/llsd+notation': llsd.format_notation,
195 'application/llsd+xml': llsd.format_xml,
196 'application/xml': llsd.format_xml,
197 }
198
199
200def formatter_for_mime_type(mime_type):
201 '''Return a formatter that encodes to the given MIME type.
202
203 The result is a pair of function and MIME type.'''
204
205 try:
206 return llsd_formatters[mime_type], mime_type
207 except KeyError:
208 raise exc.HTTPInternalServerError(
209 'Could not use MIME type %r to format response' %
210 mime_type).exception
211
212
213def formatter_for_request(req):
214 '''Return a formatter that encodes to the preferred type of the client.
215
216 The result is a pair of function and actual MIME type.'''
217
218 for ctype in req.accept.best_matches('application/llsd+xml'):
219 try:
220 return llsd_formatters[ctype], ctype
221 except KeyError:
222 pass
223 else:
224 raise exc.HTTPNotAcceptable().exception
225
226
227def wsgi_adapter(func, environ, start_response):
228 '''Adapt a Siesta callable to act as a WSGI application.'''
229
230 try:
231 req = Request(environ)
232 resp = func(req, **req.urlvars)
233 if not isinstance(resp, webob.Response):
234 try:
235 formatter, ctype = formatter_for_request(req)
236 resp = req.ResponseClass(formatter(resp), content_type=ctype)
237 resp._llsd = resp
238 except (JsonEncodeError, TypeError), err:
239 resp = exc.HTTPInternalServerError(
240 detail='Could not format response')
241 except exc.HTTPException, e:
242 resp = e
243 except socket.error, e:
244 resp = exc.HTTPInternalServerError(detail=e.args[1])
245 return resp(environ, start_response)
246
247
248def llsd_callable(func):
249 '''Turn a callable into a Siesta application.'''
250
251 def replacement(environ, start_response):
252 return wsgi_adapter(func, environ, start_response)
253
254 return replacement
255
256
257def llsd_method(http_method, func):
258 def replacement(environ, start_response):
259 if environ['REQUEST_METHOD'] == http_method:
260 return wsgi_adapter(func, environ, start_response)
261 return exc.HTTPMethodNotAllowed()(environ, start_response)
262
263 return replacement
264
265
266http11_methods = 'OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT'.split()
267http11_methods.sort()
268
269def llsd_class(cls):
270 '''Turn a class into a Siesta application.
271
272 A new instance is created for each request. A HTTP method FOO is
273 turned into a call to the handle_foo method of the instance.'''
274
275 def foo(req, **kwargs):
276 instance = cls()
277 method = req.method.lower()
278 try:
279 handler = getattr(instance, 'handle_' + method)
280 except AttributeError:
281 allowed = [m for m in http11_methods
282 if hasattr(instance, 'handle_' + m.lower())]
283 raise exc.HTTPMethodNotAllowed(
284 headers={'Allowed': ', '.join(allowed)}).exception
285 return handler(req, **kwargs)
286
287 def replacement(environ, start_response):
288 return wsgi_adapter(foo, environ, start_response)
289
290 return replacement
291
292
293def curl(reqs):
294 import pycurl
295
296 m = pycurl.CurlMulti()
297 curls = [r.curl() for r in reqs]
298 io = {}
299 for c in curls:
300 fp = StringIO()
301 hdr = StringIO()
302 c.setopt(pycurl.WRITEFUNCTION, fp.write)
303 c.setopt(pycurl.HEADERFUNCTION, hdr.write)
304 io[id(c)] = fp, hdr
305 m.handles = curls
306 try:
307 while True:
308 ret, num_handles = m.perform()
309 if ret != pycurl.E_CALL_MULTI_PERFORM:
310 break
311 finally:
312 m.close()
313
314 for req, c in zip(reqs, curls):
315 fp, hdr = io[id(c)]
316 hdr.seek(0)
317 status = hdr.readline().rstrip()
318 headers = []
319 name, values = None, None
320
321 # XXX We don't currently handle bogus header data.
322
323 for line in hdr.readlines():
324 if not line[0].isspace():
325 if name:
326 headers.append((name, ' '.join(values)))
327 name, value = line.strip().split(':', 1)
328 value = [value]
329 else:
330 values.append(line.strip())
331 if name:
332 headers.append((name, ' '.join(values)))
333
334 resp = c.ResponseClass(fp.getvalue(), status, headers, request=req)
335
336
337route_re = re.compile(r'''
338 \{ # exact character "{"
339 (\w+) # variable name (restricted to a-z, 0-9, _)
340 (?:([:~])([^}]+))? # optional :type or ~regex part
341 \} # exact character "}"
342 ''', re.VERBOSE)
343
344predefined_regexps = {
345 'uuid': r'[a-f0-9][a-f0-9-]{31,35}',
346 'int': r'\d+',
347 }
348
349def compile_route(route):
350 fp = StringIO()
351 last_pos = 0
352 for match in route_re.finditer(route):
353 fp.write(re.escape(route[last_pos:match.start()]))
354 var_name = match.group(1)
355 sep = match.group(2)
356 expr = match.group(3)
357 if expr:
358 if sep == ':':
359 expr = predefined_regexps[expr]
360 # otherwise, treat what follows '~' as a regexp
361 else:
362 expr = '[^/]+'
363 expr = '(?P<%s>%s)' % (var_name, expr)
364 fp.write(expr)
365 last_pos = match.end()
366 fp.write(re.escape(route[last_pos:]))
367 return '^%s$' % fp.getvalue()
368
369class Router(object):
370 '''WSGI routing class. Parses a URL and hands off a request to
371 some other WSGI application. If no suitable application is found,
372 responds with a 404.'''
373
374 def __init__(self):
375 self.routes = []
376 self.paths = []
377
378 def add(self, route, app, methods=None):
379 self.paths.append(route)
380 self.routes.append((re.compile(compile_route(route)), app,
381 methods and dict.fromkeys(methods)))
382
383 def __call__(self, environ, start_response):
384 path_info = environ['PATH_INFO']
385 request_method = environ['REQUEST_METHOD']
386 allowed = []
387 for regex, app, methods in self.routes:
388 m = regex.match(path_info)
389 if m:
390 if not methods or request_method in methods:
391 environ['paste.urlvars'] = m.groupdict()
392 return app(environ, start_response)
393 else:
394 allowed += methods
395 if allowed:
396 allowed = dict.fromkeys(allows).keys()
397 allowed.sort()
398 resp = exc.HTTPMethodNotAllowed(
399 headers={'Allowed': ', '.join(allowed)})
400 else:
401 resp = exc.HTTPNotFound()
402 return resp(environ, start_response)
diff --git a/linden/indra/lib/python/indra/ipc/siesta_test.py b/linden/indra/lib/python/indra/ipc/siesta_test.py
new file mode 100644
index 0000000..177ea71
--- /dev/null
+++ b/linden/indra/lib/python/indra/ipc/siesta_test.py
@@ -0,0 +1,214 @@
1from indra.base import llsd, lluuid
2from indra.ipc import siesta
3import datetime, math, unittest
4from webob import exc
5
6
7class ClassApp(object):
8 def handle_get(self, req):
9 pass
10
11 def handle_post(self, req):
12 return req.llsd
13
14
15def callable_app(req):
16 if req.method == 'UNDERPANTS':
17 raise exc.HTTPMethodNotAllowed()
18 elif req.method == 'GET':
19 return None
20 return req.llsd
21
22
23class TestBase:
24 def test_basic_get(self):
25 req = siesta.Request.blank('/')
26 self.assertEquals(req.get_response(self.server).body,
27 llsd.format_xml(None))
28
29 def test_bad_method(self):
30 req = siesta.Request.blank('/')
31 req.environ['REQUEST_METHOD'] = 'UNDERPANTS'
32 self.assertEquals(req.get_response(self.server).status_int,
33 exc.HTTPMethodNotAllowed.code)
34
35 json_safe = {
36 'none': None,
37 'bool_true': True,
38 'bool_false': False,
39 'int_zero': 0,
40 'int_max': 2147483647,
41 'int_min': -2147483648,
42 'long_zero': 0,
43 'long_max': 2147483647L,
44 'long_min': -2147483648L,
45 'float_zero': 0,
46 'float': math.pi,
47 'float_huge': 3.14159265358979323846e299,
48 'str_empty': '',
49 'str': 'foo',
50 u'unic\u1e51de_empty': u'',
51 u'unic\u1e51de': u'\u1e4exx\u10480',
52 }
53 json_safe['array'] = json_safe.values()
54 json_safe['tuple'] = tuple(json_safe.values())
55 json_safe['dict'] = json_safe.copy()
56
57 json_unsafe = {
58 'uuid_empty': lluuid.UUID(),
59 'uuid_full': lluuid.UUID('dc61ab0530200d7554d23510559102c1a98aab1b'),
60 'binary_empty': llsd.binary(),
61 'binary': llsd.binary('f\0\xff'),
62 'uri_empty': llsd.uri(),
63 'uri': llsd.uri('http://www.secondlife.com/'),
64 'datetime_empty': datetime.datetime(1970,1,1),
65 'datetime': datetime.datetime(1999,9,9,9,9,9),
66 }
67 json_unsafe.update(json_safe)
68 json_unsafe['array'] = json_unsafe.values()
69 json_unsafe['tuple'] = tuple(json_unsafe.values())
70 json_unsafe['dict'] = json_unsafe.copy()
71 json_unsafe['iter'] = iter(json_unsafe.values())
72
73 def _test_client_content_type_good(self, content_type, ll):
74 def run(ll):
75 req = siesta.Request.blank('/')
76 req.environ['REQUEST_METHOD'] = 'POST'
77 req.content_type = content_type
78 req.llsd = ll
79 req.accept = content_type
80 resp = req.get_response(self.server)
81 self.assertEquals(resp.status_int, 200)
82 return req, resp
83
84 if False and isinstance(ll, dict):
85 def fixup(v):
86 if isinstance(v, float):
87 return '%.5f' % v
88 if isinstance(v, long):
89 return int(v)
90 if isinstance(v, (llsd.binary, llsd.uri)):
91 return v
92 if isinstance(v, (tuple, list)):
93 return [fixup(i) for i in v]
94 if isinstance(v, dict):
95 return dict([(k, fixup(i)) for k, i in v.iteritems()])
96 return v
97 for k, v in ll.iteritems():
98 l = [k, v]
99 req, resp = run(l)
100 self.assertEquals(fixup(resp.llsd), fixup(l))
101
102 run(ll)
103
104 def test_client_content_type_json_good(self):
105 self._test_client_content_type_good('application/json', self.json_safe)
106
107 def test_client_content_type_llsd_xml_good(self):
108 self._test_client_content_type_good('application/llsd+xml',
109 self.json_unsafe)
110
111 def test_client_content_type_llsd_notation_good(self):
112 self._test_client_content_type_good('application/llsd+notation',
113 self.json_unsafe)
114
115 def test_client_content_type_llsd_binary_good(self):
116 self._test_client_content_type_good('application/llsd+binary',
117 self.json_unsafe)
118
119 def test_client_content_type_xml_good(self):
120 self._test_client_content_type_good('application/xml',
121 self.json_unsafe)
122
123 def _test_client_content_type_bad(self, content_type):
124 req = siesta.Request.blank('/')
125 req.environ['REQUEST_METHOD'] = 'POST'
126 req.body = '\0invalid nonsense under all encodings'
127 req.content_type = content_type
128 self.assertEquals(req.get_response(self.server).status_int,
129 exc.HTTPBadRequest.code)
130
131 def test_client_content_type_json_bad(self):
132 self._test_client_content_type_bad('application/json')
133
134 def test_client_content_type_llsd_xml_bad(self):
135 self._test_client_content_type_bad('application/llsd+xml')
136
137 def test_client_content_type_llsd_notation_bad(self):
138 self._test_client_content_type_bad('application/llsd+notation')
139
140 def test_client_content_type_llsd_binary_bad(self):
141 self._test_client_content_type_bad('application/llsd+binary')
142
143 def test_client_content_type_xml_bad(self):
144 self._test_client_content_type_bad('application/xml')
145
146 def test_client_content_type_bad(self):
147 req = siesta.Request.blank('/')
148 req.environ['REQUEST_METHOD'] = 'POST'
149 req.body = 'XXX'
150 req.content_type = 'application/nonsense'
151 self.assertEquals(req.get_response(self.server).status_int,
152 exc.HTTPUnsupportedMediaType.code)
153
154 def test_request_default_content_type(self):
155 req = siesta.Request.blank('/')
156 self.assertEquals(req.content_type, req.default_content_type)
157
158 def test_request_default_accept(self):
159 req = siesta.Request.blank('/')
160 from webob import acceptparse
161 self.assertEquals(str(req.accept).replace(' ', ''),
162 req.default_accept.replace(' ', ''))
163
164 def test_request_llsd_auto_body(self):
165 req = siesta.Request.blank('/')
166 req.llsd = {'a': 2}
167 self.assertEquals(req.body, '<?xml version="1.0" ?><llsd><map>'
168 '<key>a</key><integer>2</integer></map></llsd>')
169
170 def test_request_llsd_mod_body_changes_llsd(self):
171 req = siesta.Request.blank('/')
172 req.llsd = {'a': 2}
173 req.body = '<?xml version="1.0" ?><llsd><integer>1337</integer></llsd>'
174 self.assertEquals(req.llsd, 1337)
175
176 def test_request_bad_llsd_fails(self):
177 def crashme(ctype):
178 def boom():
179 class foo(object): pass
180 req = siesta.Request.blank('/')
181 req.content_type = ctype
182 req.llsd = foo()
183 for mime_type in siesta.llsd_parsers:
184 self.assertRaises(TypeError, crashme(mime_type))
185
186
187class ClassServer(TestBase, unittest.TestCase):
188 def __init__(self, *args, **kwargs):
189 unittest.TestCase.__init__(self, *args, **kwargs)
190 self.server = siesta.llsd_class(ClassApp)
191
192
193class CallableServer(TestBase, unittest.TestCase):
194 def __init__(self, *args, **kwargs):
195 unittest.TestCase.__init__(self, *args, **kwargs)
196 self.server = siesta.llsd_callable(callable_app)
197
198
199class RouterServer(unittest.TestCase):
200 def test_router(self):
201 def foo(req, quux):
202 print quux
203
204 r = siesta.Router()
205 r.add('/foo/{quux:int}', siesta.llsd_callable(foo), methods=['GET'])
206 req = siesta.Request.blank('/foo/33')
207 req.get_response(r)
208
209 req = siesta.Request.blank('/foo/bar')
210 self.assertEquals(req.get_response(r).status_int,
211 exc.HTTPNotFound.code)
212
213if __name__ == '__main__':
214 unittest.main()
diff --git a/linden/indra/lib/python/indra/util/fastest_elementtree.py b/linden/indra/lib/python/indra/util/fastest_elementtree.py
new file mode 100644
index 0000000..64aed09
--- /dev/null
+++ b/linden/indra/lib/python/indra/util/fastest_elementtree.py
@@ -0,0 +1,52 @@
1"""\
2@file fastest_elementtree.py
3@brief Concealing some gnarly import logic in here. This should export the interface of elementtree.
4
5$LicenseInfo:firstyear=2006&license=mit$
6
7Copyright (c) 2006-2008, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29# Using celementree might cause some unforeseen problems so here's a
30# convenient off switch.
31
32# *NOTE: turned off cause of problems. :-( *TODO: debug
33use_celementree = False
34
35try:
36 if not use_celementree:
37 raise ImportError()
38 from cElementTree import * ## This does not work under Windows
39except ImportError:
40 try:
41 if not use_celementree:
42 raise ImportError()
43 ## This is the name of cElementTree under python 2.5
44 from xml.etree.cElementTree import *
45 except ImportError:
46 try:
47 ## This is the old name of elementtree, for use with 2.3
48 from elementtree.ElementTree import *
49 except ImportError:
50 ## This is the name of elementtree under python 2.5
51 from xml.etree.ElementTree import *
52
diff --git a/linden/indra/lib/python/indra/util/llmanifest.py b/linden/indra/lib/python/indra/util/llmanifest.py
index 89c14e8..4675177 100644
--- a/linden/indra/lib/python/indra/util/llmanifest.py
+++ b/linden/indra/lib/python/indra/util/llmanifest.py
@@ -34,7 +34,6 @@ import fnmatch
34import getopt 34import getopt
35import glob 35import glob
36import os 36import os
37import os.path
38import re 37import re
39import shutil 38import shutil
40import sys 39import sys
@@ -42,10 +41,10 @@ import tarfile
42import errno 41import errno
43 42
44def path_ancestors(path): 43def path_ancestors(path):
45 path = os.path.normpath(path) 44 drive, path = os.path.splitdrive(os.path.normpath(path))
46 result = [] 45 result = []
47 while len(path) > 0: 46 while len(path) > 0 and path != os.path.sep:
48 result.append(path) 47 result.append(drive+path)
49 path, sub = os.path.split(path) 48 path, sub = os.path.split(path)
50 return result 49 return result
51 50
@@ -57,13 +56,13 @@ def proper_windows_path(path, current_platform = sys.platform):
57 drive_letter = None 56 drive_letter = None
58 rel = None 57 rel = None
59 match = re.match("/cygdrive/([a-z])/(.*)", path) 58 match = re.match("/cygdrive/([a-z])/(.*)", path)
60 if(not match): 59 if not match:
61 match = re.match('([a-zA-Z]):\\\(.*)', path) 60 match = re.match('([a-zA-Z]):\\\(.*)', path)
62 if(not match): 61 if not match:
63 return None # not an absolute path 62 return None # not an absolute path
64 drive_letter = match.group(1) 63 drive_letter = match.group(1)
65 rel = match.group(2) 64 rel = match.group(2)
66 if(current_platform == "cygwin"): 65 if current_platform == "cygwin":
67 return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\\', '/') 66 return "/cygdrive/" + drive_letter.lower() + '/' + rel.replace('\\', '/')
68 else: 67 else:
69 return drive_letter.upper() + ':\\' + rel.replace('/', '\\') 68 return drive_letter.upper() + ':\\' + rel.replace('/', '\\')
@@ -98,6 +97,7 @@ def get_channel(srctree):
98 return channel 97 return channel
99 98
100 99
100DEFAULT_SRCTREE = os.path.dirname(sys.argv[0])
101DEFAULT_CHANNEL = 'Second Life Release' 101DEFAULT_CHANNEL = 'Second Life Release'
102 102
103ARGUMENTS=[ 103ARGUMENTS=[
@@ -118,10 +118,12 @@ ARGUMENTS=[
118 Example use: %(name)s --arch=i686 118 Example use: %(name)s --arch=i686
119 On Linux this would try to use Linux_i686Manifest.""", 119 On Linux this would try to use Linux_i686Manifest.""",
120 default=""), 120 default=""),
121 dict(name='build', description='Build directory.', default=DEFAULT_SRCTREE),
121 dict(name='configuration', 122 dict(name='configuration',
122 description="""The build configuration used. Only used on OS X for 123 description="""The build configuration used. Only used on OS X for
123 now, but it could be used for other platforms as well.""", 124 now, but it could be used for other platforms as well.""",
124 default="Universal"), 125 default="Universal"),
126 dict(name='dest', description='Destination directory.', default=DEFAULT_SRCTREE),
125 dict(name='grid', 127 dict(name='grid',
126 description="""Which grid the client will try to connect to. Even 128 description="""Which grid the client will try to connect to. Even
127 though it's not strictly a grid, 'firstlook' is also an acceptable 129 though it's not strictly a grid, 'firstlook' is also an acceptable
@@ -144,6 +146,15 @@ ARGUMENTS=[
144 description="""The current platform, to be used for looking up which 146 description="""The current platform, to be used for looking up which
145 manifest class to run.""", 147 manifest class to run.""",
146 default=get_default_platform), 148 default=get_default_platform),
149 dict(name='source',
150 description='Source directory.',
151 default=DEFAULT_SRCTREE),
152 dict(name='artwork', description='Artwork directory.', default=DEFAULT_SRCTREE),
153 dict(name='touch',
154 description="""File to touch when action is finished. Touch file will
155 contain the name of the final package in a form suitable
156 for use by a .bat file.""",
157 default=None),
147 dict(name='version', 158 dict(name='version',
148 description="""This specifies the version of Second Life that is 159 description="""This specifies the version of Second Life that is
149 being packaged up.""", 160 being packaged up.""",
@@ -167,63 +178,75 @@ def usage(srctree=""):
167 default, 178 default,
168 arg['description'] % nd) 179 arg['description'] % nd)
169 180
170def main(argv=None, srctree='.', dsttree='./dst'): 181def main():
171 if(argv == None):
172 argv = sys.argv
173
174 option_names = [arg['name'] + '=' for arg in ARGUMENTS] 182 option_names = [arg['name'] + '=' for arg in ARGUMENTS]
175 option_names.append('help') 183 option_names.append('help')
176 options, remainder = getopt.getopt(argv[1:], "", option_names) 184 options, remainder = getopt.getopt(sys.argv[1:], "", option_names)
177 if len(remainder) >= 1:
178 dsttree = remainder[0]
179
180 print "Source tree:", srctree
181 print "Destination tree:", dsttree
182 185
183 # convert options to a hash 186 # convert options to a hash
184 args = {} 187 args = {'source': DEFAULT_SRCTREE,
188 'artwork': DEFAULT_SRCTREE,
189 'build': DEFAULT_SRCTREE,
190 'dest': DEFAULT_SRCTREE }
185 for opt in options: 191 for opt in options:
186 args[opt[0].replace("--", "")] = opt[1] 192 args[opt[0].replace("--", "")] = opt[1]
187 193
194 for k in 'artwork build dest source'.split():
195 args[k] = os.path.normpath(args[k])
196
197 print "Source tree:", args['source']
198 print "Artwork tree:", args['artwork']
199 print "Build tree:", args['build']
200 print "Destination tree:", args['dest']
201
188 # early out for help 202 # early out for help
189 if args.has_key('help'): 203 if 'help' in args:
190 # *TODO: it is a huge hack to pass around the srctree like this 204 # *TODO: it is a huge hack to pass around the srctree like this
191 usage(srctree) 205 usage(args['source'])
192 return 206 return
193 207
194 # defaults 208 # defaults
195 for arg in ARGUMENTS: 209 for arg in ARGUMENTS:
196 if not args.has_key(arg['name']): 210 if arg['name'] not in args:
197 default = arg['default'] 211 default = arg['default']
198 if hasattr(default, '__call__'): 212 if hasattr(default, '__call__'):
199 default = default(srctree) 213 default = default(args['source'])
200 if default is not None: 214 if default is not None:
201 args[arg['name']] = default 215 args[arg['name']] = default
202 216
203 # fix up version 217 # fix up version
204 if args.has_key('version') and type(args['version']) == str: 218 if isinstance(args.get('version'), str):
205 args['version'] = args['version'].split('.') 219 args['version'] = args['version'].split('.')
206 220
207 # default and agni are default 221 # default and agni are default
208 if args['grid'] in ['default', 'agni']: 222 if args['grid'] in ['default', 'agni']:
209 args['grid'] = '' 223 args['grid'] = ''
210 224
211 if args.has_key('actions'): 225 if 'actions' in args:
212 args['actions'] = args['actions'].split() 226 args['actions'] = args['actions'].split()
213 227
214 # debugging 228 # debugging
215 for opt in args: 229 for opt in args:
216 print "Option:", opt, "=", args[opt] 230 print "Option:", opt, "=", args[opt]
217 231
218 wm = LLManifest.for_platform(args['platform'], args.get('arch'))(srctree, dsttree, args) 232 wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)
219 wm.do(*args['actions']) 233 wm.do(*args['actions'])
234
235 # Write out the package file in this format, so that it can easily be called
236 # and used in a .bat file - yeah, it sucks, but this is the simplest...
237 touch = args.get('touch')
238 if touch:
239 fp = open(touch, 'w')
240 fp.write('set package_file=%s\n' % wm.package_file)
241 fp.close()
242 print 'touched', touch
220 return 0 243 return 0
221 244
222class LLManifestRegistry(type): 245class LLManifestRegistry(type):
223 def __init__(cls, name, bases, dct): 246 def __init__(cls, name, bases, dct):
224 super(LLManifestRegistry, cls).__init__(name, bases, dct) 247 super(LLManifestRegistry, cls).__init__(name, bases, dct)
225 match = re.match("(\w+)Manifest", name) 248 match = re.match("(\w+)Manifest", name)
226 if(match): 249 if match:
227 cls.manifests[match.group(1).lower()] = cls 250 cls.manifests[match.group(1).lower()] = cls
228 251
229class LLManifest(object): 252class LLManifest(object):
@@ -235,15 +258,18 @@ class LLManifest(object):
235 return self.manifests[platform.lower()] 258 return self.manifests[platform.lower()]
236 for_platform = classmethod(for_platform) 259 for_platform = classmethod(for_platform)
237 260
238 def __init__(self, srctree, dsttree, args): 261 def __init__(self, args):
239 super(LLManifest, self).__init__() 262 super(LLManifest, self).__init__()
240 self.args = args 263 self.args = args
241 self.file_list = [] 264 self.file_list = []
242 self.excludes = [] 265 self.excludes = []
243 self.actions = [] 266 self.actions = []
244 self.src_prefix = [srctree] 267 self.src_prefix = [args['source']]
245 self.dst_prefix = [dsttree] 268 self.artwork_prefix = [args['artwork']]
269 self.build_prefix = [args['build']]
270 self.dst_prefix = [args['dest']]
246 self.created_paths = [] 271 self.created_paths = []
272 self.package_name = "Unknown"
247 273
248 def default_grid(self): 274 def default_grid(self):
249 return self.args.get('grid', None) == '' 275 return self.args.get('grid', None) == ''
@@ -260,16 +286,20 @@ class LLManifest(object):
260 in the file list by path().""" 286 in the file list by path()."""
261 self.excludes.append(glob) 287 self.excludes.append(glob)
262 288
263 def prefix(self, src='', dst=None): 289 def prefix(self, src='', build=None, dst=None):
264 """ Pushes a prefix onto the stack. Until end_prefix is 290 """ Pushes a prefix onto the stack. Until end_prefix is
265 called, all relevant method calls (esp. to path()) will prefix 291 called, all relevant method calls (esp. to path()) will prefix
266 paths with the entire prefix stack. Source and destination 292 paths with the entire prefix stack. Source and destination
267 prefixes can be different, though if only one is provided they 293 prefixes can be different, though if only one is provided they
268 are both equal. To specify a no-op, use an empty string, not 294 are both equal. To specify a no-op, use an empty string, not
269 None.""" 295 None."""
270 if(dst == None): 296 if dst is None:
271 dst = src 297 dst = src
298 if build is None:
299 build = src
272 self.src_prefix.append(src) 300 self.src_prefix.append(src)
301 self.artwork_prefix.append(src)
302 self.build_prefix.append(build)
273 self.dst_prefix.append(dst) 303 self.dst_prefix.append(dst)
274 return True # so that you can wrap it in an if to get indentation 304 return True # so that you can wrap it in an if to get indentation
275 305
@@ -281,14 +311,24 @@ class LLManifest(object):
281 exception is raised.""" 311 exception is raised."""
282 # 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. 312 # 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.
283 src = self.src_prefix.pop() 313 src = self.src_prefix.pop()
314 artwork = self.artwork_prefix.pop()
315 build = self.build_prefix.pop()
284 dst = self.dst_prefix.pop() 316 dst = self.dst_prefix.pop()
285 if descr and not(src == descr or dst == descr): 317 if descr and not(src == descr or build == descr or dst == descr):
286 raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'" 318 raise ValueError, "End prefix '" + descr + "' didn't match '" +src+ "' or '" +dst + "'"
287 319
288 def get_src_prefix(self): 320 def get_src_prefix(self):
289 """ Returns the current source prefix.""" 321 """ Returns the current source prefix."""
290 return os.path.join(*self.src_prefix) 322 return os.path.join(*self.src_prefix)
291 323
324 def get_artwork_prefix(self):
325 """ Returns the current artwork prefix."""
326 return os.path.join(*self.artwork_prefix)
327
328 def get_build_prefix(self):
329 """ Returns the current build prefix."""
330 return os.path.join(*self.build_prefix)
331
292 def get_dst_prefix(self): 332 def get_dst_prefix(self):
293 """ Returns the current destination prefix.""" 333 """ Returns the current destination prefix."""
294 return os.path.join(*self.dst_prefix) 334 return os.path.join(*self.dst_prefix)
@@ -298,6 +338,11 @@ class LLManifest(object):
298 relative to the source directory.""" 338 relative to the source directory."""
299 return os.path.join(self.get_src_prefix(), relpath) 339 return os.path.join(self.get_src_prefix(), relpath)
300 340
341 def build_path_of(self, relpath):
342 """Returns the full path to a file or directory specified
343 relative to the build directory."""
344 return os.path.join(self.get_build_prefix(), relpath)
345
301 def dst_path_of(self, relpath): 346 def dst_path_of(self, relpath):
302 """Returns the full path to a file or directory specified 347 """Returns the full path to a file or directory specified
303 relative to the destination directory.""" 348 relative to the destination directory."""
@@ -329,13 +374,13 @@ class LLManifest(object):
329 lines = [] 374 lines = []
330 while True: 375 while True:
331 lines.append(fd.readline()) 376 lines.append(fd.readline())
332 if(lines[-1] == ''): 377 if lines[-1] == '':
333 break 378 break
334 else: 379 else:
335 print lines[-1], 380 print lines[-1],
336 output = ''.join(lines) 381 output = ''.join(lines)
337 status = fd.close() 382 status = fd.close()
338 if(status): 383 if status:
339 raise RuntimeError( 384 raise RuntimeError(
340 "Command %s returned non-zero status (%s) \noutput:\n%s" 385 "Command %s returned non-zero status (%s) \noutput:\n%s"
341 % (command, status, output) ) 386 % (command, status, output) )
@@ -356,7 +401,7 @@ class LLManifest(object):
356 f.close() 401 f.close()
357 402
358 def replace_in(self, src, dst=None, searchdict={}): 403 def replace_in(self, src, dst=None, searchdict={}):
359 if(dst == None): 404 if dst == None:
360 dst = src 405 dst = src
361 # read src 406 # read src
362 f = open(self.src_path_of(src), "rbU") 407 f = open(self.src_path_of(src), "rbU")
@@ -369,11 +414,11 @@ class LLManifest(object):
369 self.created_paths.append(dst) 414 self.created_paths.append(dst)
370 415
371 def copy_action(self, src, dst): 416 def copy_action(self, src, dst):
372 if(src and (os.path.exists(src) or os.path.islink(src))): 417 if src and (os.path.exists(src) or os.path.islink(src)):
373 # ensure that destination path exists 418 # ensure that destination path exists
374 self.cmakedirs(os.path.dirname(dst)) 419 self.cmakedirs(os.path.dirname(dst))
375 self.created_paths.append(dst) 420 self.created_paths.append(dst)
376 if(not os.path.isdir(src)): 421 if not os.path.isdir(src):
377 self.ccopy(src,dst) 422 self.ccopy(src,dst)
378 else: 423 else:
379 # src is a dir 424 # src is a dir
@@ -408,7 +453,7 @@ class LLManifest(object):
408 print "Cleaning up " + c 453 print "Cleaning up " + c
409 454
410 def process_file(self, src, dst): 455 def process_file(self, src, dst):
411 if(self.includes(src, dst)): 456 if self.includes(src, dst):
412# print src, "=>", dst 457# print src, "=>", dst
413 for action in self.actions: 458 for action in self.actions:
414 methodname = action + "_action" 459 methodname = action + "_action"
@@ -416,26 +461,29 @@ class LLManifest(object):
416 if method is not None: 461 if method is not None:
417 method(src, dst) 462 method(src, dst)
418 self.file_list.append([src, dst]) 463 self.file_list.append([src, dst])
464 return 1
419 else: 465 else:
420 print "Excluding: ", src, dst 466 sys.stdout.write(" (excluding %r, %r)" % (src, dst))
421 467 sys.stdout.flush()
468 return 0
422 469
423 def process_directory(self, src, dst): 470 def process_directory(self, src, dst):
424 if(not self.includes(src, dst)): 471 if not self.includes(src, dst):
425 print "Excluding: ", src, dst 472 sys.stdout.write(" (excluding %r, %r)" % (src, dst))
426 return 473 sys.stdout.flush()
474 return 0
427 names = os.listdir(src) 475 names = os.listdir(src)
428 self.cmakedirs(dst) 476 self.cmakedirs(dst)
429 errors = [] 477 errors = []
478 count = 0
430 for name in names: 479 for name in names:
431 srcname = os.path.join(src, name) 480 srcname = os.path.join(src, name)
432 dstname = os.path.join(dst, name) 481 dstname = os.path.join(dst, name)
433 if os.path.isdir(srcname): 482 if os.path.isdir(srcname):
434 self.process_directory(srcname, dstname) 483 count += self.process_directory(srcname, dstname)
435 else: 484 else:
436 self.process_file(srcname, dstname) 485 count += self.process_file(srcname, dstname)
437 486 return count
438
439 487
440 def includes(self, src, dst): 488 def includes(self, src, dst):
441 if src: 489 if src:
@@ -446,9 +494,9 @@ class LLManifest(object):
446 494
447 def remove(self, *paths): 495 def remove(self, *paths):
448 for path in paths: 496 for path in paths:
449 if(os.path.exists(path)): 497 if os.path.exists(path):
450 print "Removing path", path 498 print "Removing path", path
451 if(os.path.isdir(path)): 499 if os.path.isdir(path):
452 shutil.rmtree(path) 500 shutil.rmtree(path)
453 else: 501 else:
454 os.remove(path) 502 os.remove(path)
@@ -457,17 +505,17 @@ class LLManifest(object):
457 """ Copy a single file or symlink. Uses filecmp to skip copying for existing files.""" 505 """ Copy a single file or symlink. Uses filecmp to skip copying for existing files."""
458 if os.path.islink(src): 506 if os.path.islink(src):
459 linkto = os.readlink(src) 507 linkto = os.readlink(src)
460 if(os.path.islink(dst) or os.path.exists(dst)): 508 if os.path.islink(dst) or os.path.exists(dst):
461 os.remove(dst) # because symlinking over an existing link fails 509 os.remove(dst) # because symlinking over an existing link fails
462 os.symlink(linkto, dst) 510 os.symlink(linkto, dst)
463 else: 511 else:
464 # Don't recopy file if it's up-to-date. 512 # Don't recopy file if it's up-to-date.
465 # If we seem to be not not overwriting files that have been 513 # If we seem to be not not overwriting files that have been
466 # updated, set the last arg to False, but it will take longer. 514 # updated, set the last arg to False, but it will take longer.
467 if(os.path.exists(dst) and filecmp.cmp(src, dst, True)): 515 if os.path.exists(dst) and filecmp.cmp(src, dst, True):
468 return 516 return
469 # only copy if it's not excluded 517 # only copy if it's not excluded
470 if(self.includes(src, dst)): 518 if self.includes(src, dst):
471 try: 519 try:
472 os.unlink(dst) 520 os.unlink(dst)
473 except OSError, err: 521 except OSError, err:
@@ -481,7 +529,7 @@ class LLManifest(object):
481 feature that the destination directory can exist. It 529 feature that the destination directory can exist. It
482 is so dumb that Python doesn't come with this. Also it 530 is so dumb that Python doesn't come with this. Also it
483 implements the excludes functionality.""" 531 implements the excludes functionality."""
484 if(not self.includes(src, dst)): 532 if not self.includes(src, dst):
485 return 533 return
486 names = os.listdir(src) 534 names = os.listdir(src)
487 self.cmakedirs(dst) 535 self.cmakedirs(dst)
@@ -512,7 +560,7 @@ class LLManifest(object):
512 560
513 def find_existing_file(self, *list): 561 def find_existing_file(self, *list):
514 for f in list: 562 for f in list:
515 if(os.path.exists(f)): 563 if os.path.exists(f):
516 return f 564 return f
517 # didn't find it, return last item in list 565 # didn't find it, return last item in list
518 if len(list) > 0: 566 if len(list) > 0:
@@ -535,62 +583,63 @@ class LLManifest(object):
535 583
536 584
537 def wildcard_regex(self, src_glob, dst_glob): 585 def wildcard_regex(self, src_glob, dst_glob):
538 # print "regex_pair:", src_glob, dst_glob
539 src_re = re.escape(src_glob) 586 src_re = re.escape(src_glob)
540 src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)') 587 src_re = src_re.replace('\*', '([-a-zA-Z0-9._ ]+)')
541 dst_temp = dst_glob 588 dst_temp = dst_glob
542 i = 1 589 i = 1
543 while(dst_temp.count("*") > 0): 590 while dst_temp.count("*") > 0:
544 dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1) 591 dst_temp = dst_temp.replace('*', '\g<' + str(i) + '>', 1)
545 i = i+1 592 i = i+1
546 # print "regex_result:", src_re, dst_temp
547 return re.compile(src_re), dst_temp 593 return re.compile(src_re), dst_temp
548 594
549 def check_file_exists(self, path): 595 def check_file_exists(self, path):
550 if(not os.path.exists(path) and not os.path.islink(path)): 596 if not os.path.exists(path) and not os.path.islink(path):
551 raise RuntimeError("Path %s doesn't exist" % ( 597 raise RuntimeError("Path %s doesn't exist" % (
552 os.path.normpath(os.path.join(os.getcwd(), path)),)) 598 os.path.normpath(os.path.join(os.getcwd(), path)),))
553 599
554 600
555 wildcard_pattern = re.compile('\*') 601 wildcard_pattern = re.compile('\*')
556 def expand_globs(self, src, dst): 602 def expand_globs(self, src, dst):
557 def fw_slash(str):
558 return str.replace('\\', '/')
559 def os_slash(str):
560 return str.replace('/', os.path.sep)
561 dst = fw_slash(dst)
562 src = fw_slash(src)
563 src_list = glob.glob(src) 603 src_list = glob.glob(src)
564 src_re, d_template = self.wildcard_regex(src, dst) 604 src_re, d_template = self.wildcard_regex(src.replace('\\', '/'),
605 dst.replace('\\', '/'))
565 for s in src_list: 606 for s in src_list:
566 s = fw_slash(s) 607 d = src_re.sub(d_template, s.replace('\\', '/'))
567 d = src_re.sub(d_template, s) 608 yield os.path.normpath(s), os.path.normpath(d)
568 #print "s:",s, "d_t", d_template, "dst", dst, "d", d
569 yield os_slash(s), os_slash(d)
570 609
571 def path(self, src, dst=None): 610 def path(self, src, dst=None):
572 print "Processing", src, "=>", dst 611 sys.stdout.write("Processing %s => %s ... " % (src, dst))
612 sys.stdout.flush()
573 if src == None: 613 if src == None:
574 raise RuntimeError("No source file, dst is " + dst) 614 raise RuntimeError("No source file, dst is " + dst)
575 if dst == None: 615 if dst == None:
576 dst = src 616 dst = src
577 dst = os.path.join(self.get_dst_prefix(), dst) 617 dst = os.path.join(self.get_dst_prefix(), dst)
578 src = os.path.join(self.get_src_prefix(), src)
579 618
580 # expand globs 619 def try_path(src):
581 if(self.wildcard_pattern.search(src)): 620 # expand globs
582 for s,d in self.expand_globs(src, dst): 621 count = 0
583 self.process_file(s, d) 622 if self.wildcard_pattern.search(src):
584 else: 623 for s,d in self.expand_globs(src, dst):
585 # if we're specifying a single path (not a glob), 624 count += self.process_file(s, d)
586 # we should error out if it doesn't exist
587 self.check_file_exists(src)
588 # if it's a directory, recurse through it
589 if(os.path.isdir(src)):
590 self.process_directory(src, dst)
591 else: 625 else:
592 self.process_file(src, dst) 626 # if we're specifying a single path (not a glob),
593 627 # we should error out if it doesn't exist
628 self.check_file_exists(src)
629 # if it's a directory, recurse through it
630 if os.path.isdir(src):
631 count += self.process_directory(src, dst)
632 else:
633 count += self.process_file(src, dst)
634 return count
635 try:
636 count = try_path(os.path.join(self.get_src_prefix(), src))
637 except RuntimeError:
638 try:
639 count = try_path(os.path.join(self.get_artwork_prefix(), src))
640 except RuntimeError:
641 count = try_path(os.path.join(self.get_build_prefix(), src))
642 print "%d files" % count
594 643
595 def do(self, *actions): 644 def do(self, *actions):
596 self.actions = actions 645 self.actions = actions
diff --git a/linden/indra/lib/python/indra/util/named_query.py b/linden/indra/lib/python/indra/util/named_query.py
index 680d1f9..20f2ec7 100644
--- a/linden/indra/lib/python/indra/util/named_query.py
+++ b/linden/indra/lib/python/indra/util/named_query.py
@@ -47,7 +47,7 @@ except NameError:
47from indra.base import llsd 47from indra.base import llsd
48from indra.base import config 48from indra.base import config
49 49
50NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '') 50NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq')
51NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX) 51NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX)
52 52
53_g_named_manager = None 53_g_named_manager = None
@@ -60,6 +60,11 @@ def _init_g_named_manager(sql_dir = None):
60 because it's tricky to control the config from inside a test.""" 60 because it's tricky to control the config from inside a test."""
61 if sql_dir is None: 61 if sql_dir is None:
62 sql_dir = config.get('named-query-base-dir') 62 sql_dir = config.get('named-query-base-dir')
63
64 # extra fallback directory in case config doesn't return what we want
65 if sql_dir is None:
66 sql_dir = os.path.dirname(__file__) + "../../../../web/dataservice/sql"
67
63 global _g_named_manager 68 global _g_named_manager
64 _g_named_manager = NamedQueryManager( 69 _g_named_manager = NamedQueryManager(
65 os.path.abspath(os.path.realpath(sql_dir))) 70 os.path.abspath(os.path.realpath(sql_dir)))
diff --git a/linden/indra/lib/python/indra/util/term.py b/linden/indra/lib/python/indra/util/term.py
new file mode 100644
index 0000000..8238b78
--- /dev/null
+++ b/linden/indra/lib/python/indra/util/term.py
@@ -0,0 +1,222 @@
1'''
2@file term.py
3@brief a better shutil.copytree replacement
4
5$LicenseInfo:firstyear=2007&license=mit$
6
7Copyright (c) 2007-2008, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27'''
28
29#http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116
30
31import sys, re
32
33class TerminalController:
34 """
35 A class that can be used to portably generate formatted output to
36 a terminal.
37
38 `TerminalController` defines a set of instance variables whose
39 values are initialized to the control sequence necessary to
40 perform a given action. These can be simply included in normal
41 output to the terminal:
42
43 >>> term = TerminalController()
44 >>> print 'This is '+term.GREEN+'green'+term.NORMAL
45
46 Alternatively, the `render()` method can used, which replaces
47 '${action}' with the string required to perform 'action':
48
49 >>> term = TerminalController()
50 >>> print term.render('This is ${GREEN}green${NORMAL}')
51
52 If the terminal doesn't support a given action, then the value of
53 the corresponding instance variable will be set to ''. As a
54 result, the above code will still work on terminals that do not
55 support color, except that their output will not be colored.
56 Also, this means that you can test whether the terminal supports a
57 given action by simply testing the truth value of the
58 corresponding instance variable:
59
60 >>> term = TerminalController()
61 >>> if term.CLEAR_SCREEN:
62 ... print 'This terminal supports clearning the screen.'
63
64 Finally, if the width and height of the terminal are known, then
65 they will be stored in the `COLS` and `LINES` attributes.
66 """
67 # Cursor movement:
68 BOL = '' #: Move the cursor to the beginning of the line
69 UP = '' #: Move the cursor up one line
70 DOWN = '' #: Move the cursor down one line
71 LEFT = '' #: Move the cursor left one char
72 RIGHT = '' #: Move the cursor right one char
73
74 # Deletion:
75 CLEAR_SCREEN = '' #: Clear the screen and move to home position
76 CLEAR_EOL = '' #: Clear to the end of the line.
77 CLEAR_BOL = '' #: Clear to the beginning of the line.
78 CLEAR_EOS = '' #: Clear to the end of the screen
79
80 # Output modes:
81 BOLD = '' #: Turn on bold mode
82 BLINK = '' #: Turn on blink mode
83 DIM = '' #: Turn on half-bright mode
84 REVERSE = '' #: Turn on reverse-video mode
85 NORMAL = '' #: Turn off all modes
86
87 # Cursor display:
88 HIDE_CURSOR = '' #: Make the cursor invisible
89 SHOW_CURSOR = '' #: Make the cursor visible
90
91 # Terminal size:
92 COLS = None #: Width of the terminal (None for unknown)
93 LINES = None #: Height of the terminal (None for unknown)
94
95 # Foreground colors:
96 BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
97
98 # Background colors:
99 BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
100 BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
101
102 _STRING_CAPABILITIES = """
103 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
104 CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
105 BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
106 HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
107 _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
108 _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
109
110 def __init__(self, term_stream=sys.stdout):
111 """
112 Create a `TerminalController` and initialize its attributes
113 with appropriate values for the current terminal.
114 `term_stream` is the stream that will be used for terminal
115 output; if this stream is not a tty, then the terminal is
116 assumed to be a dumb terminal (i.e., have no capabilities).
117 """
118 # Curses isn't available on all platforms
119 try: import curses
120 except: return
121
122 # If the stream isn't a tty, then assume it has no capabilities.
123 if not term_stream.isatty(): return
124
125 # Check the terminal type. If we fail, then assume that the
126 # terminal has no capabilities.
127 try: curses.setupterm()
128 except: return
129
130 # Look up numeric capabilities.
131 self.COLS = curses.tigetnum('cols')
132 self.LINES = curses.tigetnum('lines')
133
134 # Look up string capabilities.
135 for capability in self._STRING_CAPABILITIES:
136 (attrib, cap_name) = capability.split('=')
137 setattr(self, attrib, self._tigetstr(cap_name) or '')
138
139 # Colors
140 set_fg = self._tigetstr('setf')
141 if set_fg:
142 for i,color in zip(range(len(self._COLORS)), self._COLORS):
143 setattr(self, color, curses.tparm(set_fg, i) or '')
144 set_fg_ansi = self._tigetstr('setaf')
145 if set_fg_ansi:
146 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
147 setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
148 set_bg = self._tigetstr('setb')
149 if set_bg:
150 for i,color in zip(range(len(self._COLORS)), self._COLORS):
151 setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
152 set_bg_ansi = self._tigetstr('setab')
153 if set_bg_ansi:
154 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
155 setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
156
157 def _tigetstr(self, cap_name):
158 # String capabilities can include "delays" of the form "$<2>".
159 # For any modern terminal, we should be able to just ignore
160 # these, so strip them out.
161 import curses
162 cap = curses.tigetstr(cap_name) or ''
163 return re.sub(r'\$<\d+>[/*]?', '', cap)
164
165 def render(self, template):
166 """
167 Replace each $-substitutions in the given template string with
168 the corresponding terminal control string (if it's defined) or
169 '' (if it's not).
170 """
171 return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
172
173 def _render_sub(self, match):
174 s = match.group()
175 if s == '$$': return s
176 else: return getattr(self, s[2:-1])
177
178#######################################################################
179# Example use case: progress bar
180#######################################################################
181
182class ProgressBar:
183 """
184 A 3-line progress bar, which looks like::
185
186 Header
187 20% [===========----------------------------------]
188 progress message
189
190 The progress bar is colored, if the terminal supports color
191 output; and adjusts to the width of the terminal.
192 """
193 BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
194 HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
195
196 def __init__(self, term, header):
197 self.term = term
198 if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
199 raise ValueError("Terminal isn't capable enough -- you "
200 "should use a simpler progress dispaly.")
201 self.width = self.term.COLS or 75
202 self.bar = term.render(self.BAR)
203 self.header = self.term.render(self.HEADER % header.center(self.width))
204 self.cleared = 1 #: true if we haven't drawn the bar yet.
205 self.update(0, '')
206
207 def update(self, percent, message):
208 if self.cleared:
209 sys.stdout.write(self.header)
210 self.cleared = 0
211 n = int((self.width-10)*percent)
212 sys.stdout.write(
213 self.term.BOL + self.term.UP + self.term.CLEAR_EOL +
214 (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) +
215 self.term.CLEAR_EOL + message.center(self.width))
216
217 def clear(self):
218 if not self.cleared:
219 sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
220 self.term.UP + self.term.CLEAR_EOL +
221 self.term.UP + self.term.CLEAR_EOL)
222 self.cleared = 1