diff options
Diffstat (limited to 'linden/indra/lib')
-rw-r--r-- | linden/indra/lib/python/indra/base/cllsd_test.py | 51 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/base/config.py | 236 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/base/llsd.py | 110 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/base/lluuid.py | 32 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/ipc/llsdhttp.py | 13 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/ipc/mysql_pool.py | 100 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/ipc/siesta.py | 402 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/ipc/siesta_test.py | 214 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/util/fastest_elementtree.py | 52 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/util/llmanifest.py | 217 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/util/named_query.py | 7 | ||||
-rw-r--r-- | linden/indra/lib/python/indra/util/term.py | 222 |
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 @@ | |||
1 | from indra.base import llsd, lluuid | ||
2 | from datetime import datetime | ||
3 | import cllsd | ||
4 | import time, sys | ||
5 | |||
6 | class myint(int): | ||
7 | pass | ||
8 | |||
9 | values = ( | ||
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 | |||
28 | def valuator(values): | ||
29 | for v in values: | ||
30 | yield v | ||
31 | |||
32 | longvalues = () # (values, list(values), iter(values), valuator(values)) | ||
33 | |||
34 | for v in values + longvalues: | ||
35 | print '%r => %r' % (v, cllsd.llsd_to_xml(v)) | ||
36 | |||
37 | a = [[{'a':3}]] * 1000000 | ||
38 | |||
39 | s = time.time() | ||
40 | print hash(cllsd.llsd_to_xml(a)) | ||
41 | e = time.time() | ||
42 | t1 = e - s | ||
43 | print t1 | ||
44 | |||
45 | s = time.time() | ||
46 | print hash(llsd.LLSDXMLFormatter()._format(a)) | ||
47 | e = time.time() | ||
48 | t2 = e - s | ||
49 | print t2 | ||
50 | |||
51 | print '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 | ||
29 | from os.path import dirname, join, realpath | 29 | import copy |
30 | import os | ||
31 | import traceback | ||
32 | import time | ||
30 | import types | 33 | import types |
34 | |||
35 | from os.path import dirname, getmtime, join, realpath | ||
31 | from indra.base import llsd | 36 | from indra.base import llsd |
32 | 37 | ||
33 | _g_config_dict = None | 38 | _g_config = None |
34 | 39 | ||
35 | def load(indra_xml_file=None): | 40 | class 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 | ||
50 | def 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 | |||
167 | def 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 | |||
185 | def 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 | ||
69 | def update(new_conf): | 211 | def 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 | ||
85 | def get(key, default = None): | 224 | def 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 | ||
91 | def set(key, newval): | 232 | def 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 | ||
97 | def as_dict(): | 247 | def 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 | |||
33 | import types | 33 | import types |
34 | import re | 34 | import re |
35 | 35 | ||
36 | #from cElementTree import fromstring ## This does not work under Windows | 36 | from indra.util.fastest_elementtree import fromstring |
37 | try: | ||
38 | ## This is the old name of elementtree, for use with 2.3 | ||
39 | from elementtree.ElementTree import fromstring | ||
40 | except ImportError: | ||
41 | ## This is the name of elementtree under python 2.5 | ||
42 | from xml.etree.ElementTree import fromstring | ||
43 | |||
44 | from indra.base import lluuid | 37 | from indra.base import lluuid |
45 | 38 | ||
46 | int_regex = re.compile("[-+]?\d+") | 39 | int_regex = re.compile("[-+]?\d+") |
@@ -67,6 +60,39 @@ BOOL_TRUE = ('1', '1.0', 'true') | |||
67 | BOOL_FALSE = ('0', '0.0', 'false', '') | 60 | BOOL_FALSE = ('0', '0.0', 'false', '') |
68 | 61 | ||
69 | 62 | ||
63 | def 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 | |||
74 | def 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 | |||
70 | def bool_to_python(node): | 96 | def 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 | ||
105 | def uri_to_python(node): | 130 | def 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 | ||
218 | def format_xml(something): | 244 | def 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 | ||
221 | class LLSDXMLPrettyFormatter(LLSDXMLFormatter): | 250 | class 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 | ||
909 | undef = LLSD(None) | 914 | undef = LLSD(None) |
910 | 915 | ||
911 | # register converters for stacked, if stacked is available | 916 | # register converters for llsd in mulib, if it is available |
912 | try: | 917 | try: |
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 | ||
915 | except: | 921 | except: |
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 | ||
917 | else: | 924 | else: |
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_ | |||
48 | request = suite.request | 48 | request = suite.request |
49 | request_ = suite.request_ | 49 | request_ = suite.request_ |
50 | 50 | ||
51 | for x in (httpc.ConnectionError, httpc.NotFound, httpc.Forbidden): | 51 | # import every httpc error exception into our namespace for convenience |
52 | for x in httpc.status_to_error_map.itervalues(): | ||
53 | globals()[x.__name__] = x | ||
54 | |||
55 | for 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_ | ||
63 | def getStatus(url, use_proxy=False): | 68 | def 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_ | |
68 | def putStatus(url, data): | 73 | def 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_ | |
73 | def deleteStatus(url): | 78 | def 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_ | |
78 | def postStatus(url, data): | 83 | def 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 | ||
29 | import os | ||
30 | |||
31 | from eventlet.pools import Pool | ||
32 | from eventlet.processes import DeadProcess | ||
33 | from indra.ipc import saranwrap | ||
34 | |||
35 | import MySQLdb | 29 | import MySQLdb |
30 | from eventlet import db_pool | ||
36 | 31 | ||
37 | # method 2: better -- admits the existence of the pool | 32 | class 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 | |||
45 | class DatabaseConnector(object): | ||
46 | """\ | ||
47 | @brief This is an object which will maintain a collection of database | ||
48 | connection 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 | 50 | class ConnectionPool(db_pool.TpooledConnectionPool): | |
81 | class 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 @@ | |||
1 | from indra.base import llsd | ||
2 | from webob import exc | ||
3 | import webob | ||
4 | import re, socket | ||
5 | |||
6 | try: | ||
7 | from cStringIO import StringIO | ||
8 | except ImportError: | ||
9 | from StringIO import StringIO | ||
10 | |||
11 | try: | ||
12 | import cjson | ||
13 | json_decode = cjson.decode | ||
14 | json_encode = cjson.encode | ||
15 | JsonDecodeError = cjson.DecodeError | ||
16 | JsonEncodeError = cjson.EncodeError | ||
17 | except ImportError: | ||
18 | import simplejson | ||
19 | json_decode = simplejson.loads | ||
20 | json_encode = simplejson.dumps | ||
21 | JsonDecodeError = ValueError | ||
22 | JsonEncodeError = TypeError | ||
23 | |||
24 | |||
25 | llsd_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 | |||
34 | def 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 | |||
39 | class 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 | |||
86 | class 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 | |||
112 | class 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 | |||
187 | Request.ResponseClass = Response | ||
188 | Response.RequestClass = Request | ||
189 | |||
190 | |||
191 | llsd_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 | |||
200 | def 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 | |||
213 | def 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 | |||
227 | def 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 | |||
248 | def 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 | |||
257 | def 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 | |||
266 | http11_methods = 'OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT'.split() | ||
267 | http11_methods.sort() | ||
268 | |||
269 | def 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 | |||
293 | def 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 | |||
337 | route_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 | |||
344 | predefined_regexps = { | ||
345 | 'uuid': r'[a-f0-9][a-f0-9-]{31,35}', | ||
346 | 'int': r'\d+', | ||
347 | } | ||
348 | |||
349 | def 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 | |||
369 | class 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 @@ | |||
1 | from indra.base import llsd, lluuid | ||
2 | from indra.ipc import siesta | ||
3 | import datetime, math, unittest | ||
4 | from webob import exc | ||
5 | |||
6 | |||
7 | class ClassApp(object): | ||
8 | def handle_get(self, req): | ||
9 | pass | ||
10 | |||
11 | def handle_post(self, req): | ||
12 | return req.llsd | ||
13 | |||
14 | |||
15 | def 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 | |||
23 | class 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 | |||
187 | class 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 | |||
193 | class 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 | |||
199 | class 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 | |||
213 | if __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 | |||
7 | Copyright (c) 2006-2008, Linden Research, Inc. | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE 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 | ||
33 | use_celementree = False | ||
34 | |||
35 | try: | ||
36 | if not use_celementree: | ||
37 | raise ImportError() | ||
38 | from cElementTree import * ## This does not work under Windows | ||
39 | except 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 | |||
34 | import getopt | 34 | import getopt |
35 | import glob | 35 | import glob |
36 | import os | 36 | import os |
37 | import os.path | ||
38 | import re | 37 | import re |
39 | import shutil | 38 | import shutil |
40 | import sys | 39 | import sys |
@@ -42,10 +41,10 @@ import tarfile | |||
42 | import errno | 41 | import errno |
43 | 42 | ||
44 | def path_ancestors(path): | 43 | def 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 | ||
100 | DEFAULT_SRCTREE = os.path.dirname(sys.argv[0]) | ||
101 | DEFAULT_CHANNEL = 'Second Life Release' | 101 | DEFAULT_CHANNEL = 'Second Life Release' |
102 | 102 | ||
103 | ARGUMENTS=[ | 103 | ARGUMENTS=[ |
@@ -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 | ||
170 | def main(argv=None, srctree='.', dsttree='./dst'): | 181 | def 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 | ||
222 | class LLManifestRegistry(type): | 245 | class 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 | ||
229 | class LLManifest(object): | 252 | class 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: | |||
47 | from indra.base import llsd | 47 | from indra.base import llsd |
48 | from indra.base import config | 48 | from indra.base import config |
49 | 49 | ||
50 | NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '') | 50 | NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq') |
51 | NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX) | 51 | NQ_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 | |||
7 | Copyright (c) 2007-2008, Linden Research, Inc. | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | $/LicenseInfo$ | ||
27 | ''' | ||
28 | |||
29 | #http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116 | ||
30 | |||
31 | import sys, re | ||
32 | |||
33 | class 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 | |||
182 | class 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 | ||