diff options
author | Jacek Antonelli | 2008-08-15 23:45:11 -0500 |
---|---|---|
committer | Jacek Antonelli | 2008-08-15 23:45:11 -0500 |
commit | 215f423cbe18fe9ca14a26caef918d303bad28ff (patch) | |
tree | 0743442b286216cc8e19aa487c26f4e9345ffd64 /linden/indra/lib | |
parent | Second Life viewer sources 1.18.3.5-RC (diff) | |
download | meta-impy-215f423cbe18fe9ca14a26caef918d303bad28ff.zip meta-impy-215f423cbe18fe9ca14a26caef918d303bad28ff.tar.gz meta-impy-215f423cbe18fe9ca14a26caef918d303bad28ff.tar.bz2 meta-impy-215f423cbe18fe9ca14a26caef918d303bad28ff.tar.xz |
Second Life viewer sources 1.18.4.0-RC
Diffstat (limited to 'linden/indra/lib')
22 files changed, 3709 insertions, 140 deletions
diff --git a/linden/indra/lib/python/indra/__init__.py b/linden/indra/lib/python/indra/__init__.py index 220768f..353a93f 100644 --- a/linden/indra/lib/python/indra/__init__.py +++ b/linden/indra/lib/python/indra/__init__.py | |||
@@ -2,26 +2,19 @@ | |||
2 | @file __init__.py | 2 | @file __init__.py |
3 | @brief Initialization file for the indra module. | 3 | @brief Initialization file for the indra module. |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2006&license=internal$ | ||
6 | |||
5 | Copyright (c) 2006-2007, Linden Research, Inc. | 7 | Copyright (c) 2006-2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | The following source code is PROPRIETARY AND CONFIDENTIAL. Use of |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | this source code is governed by the Linden Lab Source Code Disclosure |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | Agreement ("Agreement") previously entered between you and Linden |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | Lab. By accessing, using, copying, modifying or distributing this |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | software, you acknowledge that you have been informed of your |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | obligations under the Agreement and agree to abide by those obligations. |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | COMPLETENESS OR PERFORMANCE. |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | $/LicenseInfo$ |
18 | # online at http://secondlife.com/developers/opensource/flossexception | ||
19 | # | ||
20 | # By copying, modifying or distributing this software, you acknowledge | ||
21 | # that you have read and understood your obligations described above, | ||
22 | # and agree to abide by those obligations. | ||
23 | # | ||
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 20 | """ |
diff --git a/linden/indra/lib/python/indra/base/__init__.py b/linden/indra/lib/python/indra/base/__init__.py new file mode 100644 index 0000000..913164d --- /dev/null +++ b/linden/indra/lib/python/indra/base/__init__.py | |||
@@ -0,0 +1,27 @@ | |||
1 | """\ | ||
2 | @file __init__.py | ||
3 | @brief Initialization file for the indra.base module. | ||
4 | |||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
7 | Copyright (c) 2007, 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 | """ | ||
diff --git a/linden/indra/lib/python/indra/base/config.py b/linden/indra/lib/python/indra/base/config.py new file mode 100644 index 0000000..c6872fa --- /dev/null +++ b/linden/indra/lib/python/indra/base/config.py | |||
@@ -0,0 +1,74 @@ | |||
1 | """\ | ||
2 | @file config.py | ||
3 | @brief Utility module for parsing and accessing the indra.xml config file. | ||
4 | |||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
7 | Copyright (c) 2006-2007, 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 | from os.path import dirname, join, realpath | ||
30 | import types | ||
31 | from indra.base import llsd | ||
32 | |||
33 | _g_config_dict = None | ||
34 | |||
35 | def load(indra_xml_file=None): | ||
36 | global _g_config_dict | ||
37 | if _g_config_dict == None: | ||
38 | if indra_xml_file is None: | ||
39 | ## going from: | ||
40 | ## "/opt/linden/indra/lib/python/indra/base/config.py" | ||
41 | ## to: | ||
42 | ## "/opt/linden/etc/indra.xml" | ||
43 | indra_xml_file = realpath( | ||
44 | dirname(realpath(__file__)) + "../../../../../../etc/indra.xml") | ||
45 | config_file = file(indra_xml_file) | ||
46 | _g_config_dict = llsd.LLSD().parse(config_file.read()) | ||
47 | config_file.close() | ||
48 | #print "loaded config from",indra_xml_file,"into",_g_config_dict | ||
49 | |||
50 | def update(new_conf): | ||
51 | """Load an XML file and apply its map as overrides or additions | ||
52 | to the existing config. The dataserver does this with indra.xml | ||
53 | and dataserver.xml.""" | ||
54 | global _g_config_dict | ||
55 | if _g_config_dict == None: | ||
56 | _g_config_dict = {} | ||
57 | if isinstance(new_conf, dict): | ||
58 | overrides = new_conf | ||
59 | else: | ||
60 | config_file = file(new_conf) | ||
61 | overrides = llsd.LLSD().parse(config_file.read()) | ||
62 | config_file.close() | ||
63 | |||
64 | _g_config_dict.update(overrides) | ||
65 | |||
66 | def get(key, default = None): | ||
67 | global _g_config_dict | ||
68 | if _g_config_dict == None: | ||
69 | load() | ||
70 | return _g_config_dict.get(key, default) | ||
71 | |||
72 | def as_dict(): | ||
73 | global _g_config_dict | ||
74 | return _g_config_dict | ||
diff --git a/linden/indra/lib/python/indra/base/llsd.py b/linden/indra/lib/python/indra/base/llsd.py new file mode 100644 index 0000000..9e636ea --- /dev/null +++ b/linden/indra/lib/python/indra/base/llsd.py | |||
@@ -0,0 +1,870 @@ | |||
1 | """\ | ||
2 | @file llsd.py | ||
3 | @brief Types as well as parsing and formatting functions for handling LLSD. | ||
4 | |||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
7 | Copyright (c) 2006-2007, 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 | import datetime | ||
30 | import base64 | ||
31 | import struct | ||
32 | import time | ||
33 | import types | ||
34 | import re | ||
35 | |||
36 | #from cElementTree import fromstring ## This does not work under Windows | ||
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 | ||
45 | |||
46 | int_regex = re.compile("[-+]?\d+") | ||
47 | real_regex = re.compile("[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") | ||
48 | alpha_regex = re.compile("[a-zA-Z]+") | ||
49 | date_regex = re.compile("(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<second_float>\.\d{2})?Z") | ||
50 | #date: d"YYYY-MM-DDTHH:MM:SS.FFZ" | ||
51 | |||
52 | class LLSDParseError(Exception): | ||
53 | pass | ||
54 | |||
55 | class LLSDSerializationError(Exception): | ||
56 | pass | ||
57 | |||
58 | |||
59 | class binary(str): | ||
60 | pass | ||
61 | |||
62 | class uri(str): | ||
63 | pass | ||
64 | |||
65 | |||
66 | BOOL_TRUE = ('1', '1.0', 'true') | ||
67 | BOOL_FALSE = ('0', '0.0', 'false', '') | ||
68 | |||
69 | |||
70 | def bool_to_python(node): | ||
71 | val = node.text or '' | ||
72 | if val in BOOL_TRUE: | ||
73 | return True | ||
74 | else: | ||
75 | return False | ||
76 | |||
77 | def int_to_python(node): | ||
78 | val = node.text or '' | ||
79 | if not val.strip(): | ||
80 | return 0 | ||
81 | return int(val) | ||
82 | |||
83 | def real_to_python(node): | ||
84 | val = node.text or '' | ||
85 | if not val.strip(): | ||
86 | return 0.0 | ||
87 | return float(val) | ||
88 | |||
89 | def uuid_to_python(node): | ||
90 | return lluuid.UUID(node.text) | ||
91 | |||
92 | def str_to_python(node): | ||
93 | return unicode(node.text or '').encode('utf8', 'replace') | ||
94 | |||
95 | def bin_to_python(node): | ||
96 | return binary(base64.decodestring(node.text or '')) | ||
97 | |||
98 | def date_to_python(node): | ||
99 | val = node.text or '' | ||
100 | if not val: | ||
101 | val = "1970-01-01T00:00:00Z" | ||
102 | return datetime.datetime( | ||
103 | *time.strptime(val, '%Y-%m-%dT%H:%M:%SZ')[:6]) | ||
104 | |||
105 | def uri_to_python(node): | ||
106 | val = node.text or '' | ||
107 | if not val: | ||
108 | return None | ||
109 | return uri(val) | ||
110 | |||
111 | def map_to_python(node): | ||
112 | result = {} | ||
113 | for index in range(len(node))[::2]: | ||
114 | result[node[index].text] = to_python(node[index+1]) | ||
115 | return result | ||
116 | |||
117 | def array_to_python(node): | ||
118 | return [to_python(child) for child in node] | ||
119 | |||
120 | |||
121 | NODE_HANDLERS = dict( | ||
122 | undef=lambda x: None, | ||
123 | boolean=bool_to_python, | ||
124 | integer=int_to_python, | ||
125 | real=real_to_python, | ||
126 | uuid=uuid_to_python, | ||
127 | string=str_to_python, | ||
128 | binary=bin_to_python, | ||
129 | date=date_to_python, | ||
130 | uri=uri_to_python, | ||
131 | map=map_to_python, | ||
132 | array=array_to_python, | ||
133 | ) | ||
134 | |||
135 | def to_python(node): | ||
136 | return NODE_HANDLERS[node.tag](node) | ||
137 | |||
138 | class Nothing(object): | ||
139 | pass | ||
140 | |||
141 | |||
142 | class LLSDXMLFormatter(object): | ||
143 | def __init__(self): | ||
144 | self.type_map = { | ||
145 | type(None) : self.UNDEF, | ||
146 | bool : self.BOOLEAN, | ||
147 | int : self.INTEGER, | ||
148 | long : self.INTEGER, | ||
149 | float : self.REAL, | ||
150 | lluuid.UUID : self.UUID, | ||
151 | binary : self.BINARY, | ||
152 | str : self.STRING, | ||
153 | unicode : self.STRING, | ||
154 | uri : self.URI, | ||
155 | datetime.datetime : self.DATE, | ||
156 | list : self.ARRAY, | ||
157 | tuple : self.ARRAY, | ||
158 | types.GeneratorType : self.ARRAY, | ||
159 | dict : self.MAP, | ||
160 | LLSD : self.LLSD | ||
161 | } | ||
162 | |||
163 | def elt(self, name, contents=None): | ||
164 | if(contents is None or contents is ''): | ||
165 | return "<%s />" % (name,) | ||
166 | else: | ||
167 | return "<%s>%s</%s>" % (name, contents, name) | ||
168 | |||
169 | def xml_esc(self, v): | ||
170 | return v.replace('&', '&').replace('<', '<').replace('>', '>') | ||
171 | |||
172 | def LLSD(self, v): | ||
173 | return self.generate(v.thing) | ||
174 | def UNDEF(self, v): | ||
175 | return self.elt('undef') | ||
176 | def BOOLEAN(self, v): | ||
177 | if v: | ||
178 | return self.elt('boolean', 'true') | ||
179 | else: | ||
180 | return self.elt('boolean', 'false') | ||
181 | def INTEGER(self, v): | ||
182 | return self.elt('integer', v) | ||
183 | def REAL(self, v): | ||
184 | return self.elt('real', v) | ||
185 | def UUID(self, v): | ||
186 | if(v.isNull()): | ||
187 | return self.elt('uuid') | ||
188 | else: | ||
189 | return self.elt('uuid', v) | ||
190 | def BINARY(self, v): | ||
191 | return self.elt('binary', base64.encodestring(v)) | ||
192 | def STRING(self, v): | ||
193 | return self.elt('string', self.xml_esc(v)) | ||
194 | def URI(self, v): | ||
195 | return self.elt('uri', self.xml_esc(str(v))) | ||
196 | def DATE(self, v): | ||
197 | return self.elt('date', v.strftime('%Y-%m-%dT%H:%M:%SZ')) | ||
198 | def ARRAY(self, v): | ||
199 | return self.elt('array', ''.join([self.generate(item) for item in v])) | ||
200 | def MAP(self, v): | ||
201 | return self.elt( | ||
202 | 'map', | ||
203 | ''.join(["%s%s" % (self.elt('key', key), self.generate(value)) | ||
204 | for key, value in v.items()])) | ||
205 | |||
206 | typeof = type | ||
207 | def generate(self, something): | ||
208 | t = self.typeof(something) | ||
209 | if self.type_map.has_key(t): | ||
210 | return self.type_map[t](something) | ||
211 | else: | ||
212 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | ||
213 | t, something)) | ||
214 | |||
215 | def format(self, something): | ||
216 | return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) | ||
217 | |||
218 | def format_xml(something): | ||
219 | return LLSDXMLFormatter().format(something) | ||
220 | |||
221 | class LLSDNotationFormatter(object): | ||
222 | def __init__(self): | ||
223 | self.type_map = { | ||
224 | type(None) : self.UNDEF, | ||
225 | bool : self.BOOLEAN, | ||
226 | int : self.INTEGER, | ||
227 | long : self.INTEGER, | ||
228 | float : self.REAL, | ||
229 | lluuid.UUID : self.UUID, | ||
230 | binary : self.BINARY, | ||
231 | str : self.STRING, | ||
232 | unicode : self.STRING, | ||
233 | uri : self.URI, | ||
234 | datetime.datetime : self.DATE, | ||
235 | list : self.ARRAY, | ||
236 | tuple : self.ARRAY, | ||
237 | types.GeneratorType : self.ARRAY, | ||
238 | dict : self.MAP, | ||
239 | LLSD : self.LLSD | ||
240 | } | ||
241 | |||
242 | def LLSD(self, v): | ||
243 | return self.generate(v.thing) | ||
244 | def UNDEF(self, v): | ||
245 | return '!' | ||
246 | def BOOLEAN(self, v): | ||
247 | if v: | ||
248 | return 'true' | ||
249 | else: | ||
250 | return 'false' | ||
251 | def INTEGER(self, v): | ||
252 | return "i%s" % v | ||
253 | def REAL(self, v): | ||
254 | return "r%s" % v | ||
255 | def UUID(self, v): | ||
256 | return "u%s" % v | ||
257 | def BINARY(self, v): | ||
258 | raise LLSDSerializationError("binary notation not yet supported") | ||
259 | def STRING(self, v): | ||
260 | return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") | ||
261 | def URI(self, v): | ||
262 | return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') | ||
263 | def DATE(self, v): | ||
264 | second_str = "" | ||
265 | if v.microsecond > 0: | ||
266 | seconds = v.second + float(v.microsecond) / 1000000 | ||
267 | second_str = "%05.2f" % seconds | ||
268 | else: | ||
269 | second_str = "%d" % v.second | ||
270 | return 'd"%s%sZ"' % (v.strftime('%Y-%m-%dT%H:%M:'), second_str) | ||
271 | def ARRAY(self, v): | ||
272 | return "[%s]" % ','.join([self.generate(item) for item in v]) | ||
273 | def MAP(self, v): | ||
274 | return "{%s}" % ','.join(["'%s':%s" % (key.replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) | ||
275 | for key, value in v.items()]) | ||
276 | |||
277 | def generate(self, something): | ||
278 | t = type(something) | ||
279 | if self.type_map.has_key(t): | ||
280 | return self.type_map[t](something) | ||
281 | else: | ||
282 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | ||
283 | t, something)) | ||
284 | |||
285 | def format(self, something): | ||
286 | return self.generate(something) | ||
287 | |||
288 | def format_notation(something): | ||
289 | return LLSDNotationFormatter().format(something) | ||
290 | |||
291 | def _hex_as_nybble(hex): | ||
292 | if (hex >= '0') and (hex <= '9'): | ||
293 | return ord(hex) - ord('0') | ||
294 | elif (hex >= 'a') and (hex <='f'): | ||
295 | return 10 + ord(hex) - ord('a') | ||
296 | elif (hex >= 'A') and (hex <='F'): | ||
297 | return 10 + ord(hex) - ord('A'); | ||
298 | |||
299 | class LLSDBinaryParser(object): | ||
300 | def __init__(self): | ||
301 | pass | ||
302 | |||
303 | def parse(self, buffer, ignore_binary = False): | ||
304 | """ | ||
305 | This is the basic public interface for parsing. | ||
306 | |||
307 | @param buffer the binary data to parse in an indexable sequence. | ||
308 | @param ignore_binary parser throws away data in llsd binary nodes. | ||
309 | @return returns a python object. | ||
310 | """ | ||
311 | self._buffer = buffer | ||
312 | self._index = 0 | ||
313 | self._keep_binary = not ignore_binary | ||
314 | return self._parse() | ||
315 | |||
316 | def _parse(self): | ||
317 | cc = self._buffer[self._index] | ||
318 | self._index += 1 | ||
319 | if cc == '{': | ||
320 | return self._parse_map() | ||
321 | elif cc == '[': | ||
322 | return self._parse_array() | ||
323 | elif cc == '!': | ||
324 | return None | ||
325 | elif cc == '0': | ||
326 | return False | ||
327 | elif cc == '1': | ||
328 | return True | ||
329 | elif cc == 'i': | ||
330 | # 'i' = integer | ||
331 | idx = self._index | ||
332 | self._index += 4 | ||
333 | return struct.unpack("!i", self._buffer[idx:idx+4])[0] | ||
334 | elif cc == ('r'): | ||
335 | # 'r' = real number | ||
336 | idx = self._index | ||
337 | self._index += 8 | ||
338 | return struct.unpack("!d", self._buffer[idx:idx+8])[0] | ||
339 | elif cc == 'u': | ||
340 | # 'u' = uuid | ||
341 | idx = self._index | ||
342 | self._index += 16 | ||
343 | return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) | ||
344 | elif cc == 's': | ||
345 | # 's' = string | ||
346 | return self._parse_string() | ||
347 | elif cc in ("'", '"'): | ||
348 | # delimited/escaped string | ||
349 | return self._parse_string_delim(cc) | ||
350 | elif cc == 'l': | ||
351 | # 'l' = uri | ||
352 | return uri(self._parse_string()) | ||
353 | elif cc == ('d'): | ||
354 | # 'd' = date in seconds since epoch | ||
355 | idx = self._index | ||
356 | self._index += 8 | ||
357 | seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] | ||
358 | return datetime.datetime.fromtimestamp(seconds) | ||
359 | elif cc == 'b': | ||
360 | binary = self._parse_string() | ||
361 | if self._keep_binary: | ||
362 | return binary | ||
363 | # *NOTE: maybe have a binary placeholder which has the | ||
364 | # length. | ||
365 | return None | ||
366 | else: | ||
367 | raise LLSDParseError("invalid binary token at byte %d: %d" % ( | ||
368 | self._index - 1, ord(cc))) | ||
369 | |||
370 | def _parse_map(self): | ||
371 | rv = {} | ||
372 | size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] | ||
373 | self._index += 4 | ||
374 | count = 0 | ||
375 | cc = self._buffer[self._index] | ||
376 | self._index += 1 | ||
377 | key = '' | ||
378 | while (cc != '}') and (count < size): | ||
379 | if cc == 'k': | ||
380 | key = self._parse_string() | ||
381 | elif cc in ("'", '"'): | ||
382 | key = self._parse_string_delim(cc) | ||
383 | else: | ||
384 | raise LLSDParseError("invalid map key at byte %d." % ( | ||
385 | self._index - 1,)) | ||
386 | value = self._parse() | ||
387 | #print "kv:",key,value | ||
388 | rv[key] = value | ||
389 | count += 1 | ||
390 | cc = self._buffer[self._index] | ||
391 | self._index += 1 | ||
392 | if cc != '}': | ||
393 | raise LLSDParseError("invalid map close token at byte %d." % ( | ||
394 | self._index,)) | ||
395 | return rv | ||
396 | |||
397 | def _parse_array(self): | ||
398 | rv = [] | ||
399 | size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] | ||
400 | self._index += 4 | ||
401 | count = 0 | ||
402 | cc = self._buffer[self._index] | ||
403 | while (cc != ']') and (count < size): | ||
404 | rv.append(self._parse()) | ||
405 | count += 1 | ||
406 | cc = self._buffer[self._index] | ||
407 | if cc != ']': | ||
408 | raise LLSDParseError("invalid array close token at byte %d." % ( | ||
409 | self._index,)) | ||
410 | self._index += 1 | ||
411 | return rv | ||
412 | |||
413 | def _parse_string(self): | ||
414 | size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] | ||
415 | self._index += 4 | ||
416 | rv = self._buffer[self._index:self._index+size] | ||
417 | self._index += size | ||
418 | return rv | ||
419 | |||
420 | def _parse_string_delim(self, delim): | ||
421 | list = [] | ||
422 | found_escape = False | ||
423 | found_hex = False | ||
424 | found_digit = False | ||
425 | byte = 0 | ||
426 | while True: | ||
427 | cc = self._buffer[self._index] | ||
428 | self._index += 1 | ||
429 | if found_escape: | ||
430 | if found_hex: | ||
431 | if found_digit: | ||
432 | found_escape = False | ||
433 | found_hex = False | ||
434 | found_digit = False | ||
435 | byte <<= 4 | ||
436 | byte |= _hex_as_nybble(cc) | ||
437 | list.append(chr(byte)) | ||
438 | byte = 0 | ||
439 | else: | ||
440 | found_digit = True | ||
441 | byte = _hex_as_nybble(cc) | ||
442 | elif cc == 'x': | ||
443 | found_hex = True | ||
444 | else: | ||
445 | if cc == 'a': | ||
446 | list.append('\a') | ||
447 | elif cc == 'b': | ||
448 | list.append('\b') | ||
449 | elif cc == 'f': | ||
450 | list.append('\f') | ||
451 | elif cc == 'n': | ||
452 | list.append('\n') | ||
453 | elif cc == 'r': | ||
454 | list.append('\r') | ||
455 | elif cc == 't': | ||
456 | list.append('\t') | ||
457 | elif cc == 'v': | ||
458 | list.append('\v') | ||
459 | else: | ||
460 | list.append(cc) | ||
461 | found_escape = False | ||
462 | elif cc == '\\': | ||
463 | found_escape = True | ||
464 | elif cc == delim: | ||
465 | break | ||
466 | else: | ||
467 | list.append(cc) | ||
468 | return ''.join(list) | ||
469 | |||
470 | class LLSDNotationParser(object): | ||
471 | """ Parse LLSD notation: | ||
472 | map: { string:object, string:object } | ||
473 | array: [ object, object, object ] | ||
474 | undef: ! | ||
475 | boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE | ||
476 | integer: i#### | ||
477 | real: r#### | ||
478 | uuid: u#### | ||
479 | string: "g'day" | 'have a "nice" day' | s(size)"raw data" | ||
480 | uri: l"escaped" | ||
481 | date: d"YYYY-MM-DDTHH:MM:SS.FFZ" | ||
482 | binary: b##"ff3120ab1" | b(size)"raw data" """ | ||
483 | def __init__(self): | ||
484 | pass | ||
485 | |||
486 | def parse(self, buffer, ignore_binary = False): | ||
487 | """ | ||
488 | This is the basic public interface for parsing. | ||
489 | |||
490 | @param buffer the notation string to parse. | ||
491 | @param ignore_binary parser throws away data in llsd binary nodes. | ||
492 | @return returns a python object. | ||
493 | """ | ||
494 | if buffer == "": | ||
495 | return False | ||
496 | |||
497 | self._buffer = buffer | ||
498 | self._index = 0 | ||
499 | return self._parse() | ||
500 | |||
501 | def _parse(self): | ||
502 | cc = self._buffer[self._index] | ||
503 | self._index += 1 | ||
504 | if cc == '{': | ||
505 | return self._parse_map() | ||
506 | elif cc == '[': | ||
507 | return self._parse_array() | ||
508 | elif cc == '!': | ||
509 | return None | ||
510 | elif cc == '0': | ||
511 | return False | ||
512 | elif cc == '1': | ||
513 | return True | ||
514 | elif cc in ('F', 'f'): | ||
515 | self._skip_alpha() | ||
516 | return False | ||
517 | elif cc in ('T', 't'): | ||
518 | self._skip_alpha() | ||
519 | return True | ||
520 | elif cc == 'i': | ||
521 | # 'i' = integer | ||
522 | return self._parse_integer() | ||
523 | elif cc == ('r'): | ||
524 | # 'r' = real number | ||
525 | return self._parse_real() | ||
526 | elif cc == 'u': | ||
527 | # 'u' = uuid | ||
528 | return self._parse_uuid() | ||
529 | elif cc in ("'", '"', 's'): | ||
530 | return self._parse_string(cc) | ||
531 | elif cc == 'l': | ||
532 | # 'l' = uri | ||
533 | delim = self._buffer[self._index] | ||
534 | self._index += 1 | ||
535 | val = uri(self._parse_string(delim)) | ||
536 | if len(val) == 0: | ||
537 | return None | ||
538 | return val | ||
539 | elif cc == ('d'): | ||
540 | # 'd' = date in seconds since epoch | ||
541 | return self._parse_date() | ||
542 | elif cc == 'b': | ||
543 | raise LLSDParseError("binary notation not yet supported") | ||
544 | else: | ||
545 | print cc | ||
546 | raise LLSDParseError("invalid token at index %d: %d" % ( | ||
547 | self._index - 1, ord(cc))) | ||
548 | |||
549 | def _parse_map(self): | ||
550 | """ map: { string:object, string:object } """ | ||
551 | rv = {} | ||
552 | cc = self._buffer[self._index] | ||
553 | self._index += 1 | ||
554 | key = '' | ||
555 | found_key = False | ||
556 | while (cc != '}'): | ||
557 | if not found_key: | ||
558 | if cc in ("'", '"', 's'): | ||
559 | key = self._parse_string(cc) | ||
560 | found_key = True | ||
561 | #print "key:",key | ||
562 | elif cc.isspace() or cc == ',': | ||
563 | cc = self._buffer[self._index] | ||
564 | self._index += 1 | ||
565 | else: | ||
566 | raise LLSDParseError("invalid map key at byte %d." % ( | ||
567 | self._index - 1,)) | ||
568 | else: | ||
569 | if cc.isspace() or cc == ':': | ||
570 | #print "skipping whitespace '%s'" % cc | ||
571 | cc = self._buffer[self._index] | ||
572 | self._index += 1 | ||
573 | continue | ||
574 | self._index += 1 | ||
575 | value = self._parse() | ||
576 | #print "kv:",key,value | ||
577 | rv[key] = value | ||
578 | found_key = False | ||
579 | cc = self._buffer[self._index] | ||
580 | self._index += 1 | ||
581 | #if cc == '}': | ||
582 | # break | ||
583 | #cc = self._buffer[self._index] | ||
584 | #self._index += 1 | ||
585 | |||
586 | return rv | ||
587 | |||
588 | def _parse_array(self): | ||
589 | """ array: [ object, object, object ] """ | ||
590 | rv = [] | ||
591 | cc = self._buffer[self._index] | ||
592 | while (cc != ']'): | ||
593 | if cc.isspace() or cc == ',': | ||
594 | self._index += 1 | ||
595 | cc = self._buffer[self._index] | ||
596 | continue | ||
597 | rv.append(self._parse()) | ||
598 | cc = self._buffer[self._index] | ||
599 | |||
600 | if cc != ']': | ||
601 | raise LLSDParseError("invalid array close token at index %d." % ( | ||
602 | self._index,)) | ||
603 | self._index += 1 | ||
604 | return rv | ||
605 | |||
606 | def _parse_uuid(self): | ||
607 | match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) | ||
608 | if not match: | ||
609 | raise LLSDParseError("invalid uuid token at index %d." % self._index) | ||
610 | |||
611 | (start, end) = match.span() | ||
612 | start += self._index | ||
613 | end += self._index | ||
614 | self._index = end | ||
615 | return lluuid.UUID(self._buffer[start:end]) | ||
616 | |||
617 | def _skip_alpha(self): | ||
618 | match = re.match(alpha_regex, self._buffer[self._index:]) | ||
619 | if match: | ||
620 | self._index += match.end() | ||
621 | |||
622 | def _parse_date(self): | ||
623 | delim = self._buffer[self._index] | ||
624 | self._index += 1 | ||
625 | datestr = self._parse_string(delim) | ||
626 | |||
627 | if datestr == "": | ||
628 | return datetime.datetime(1970, 1, 1) | ||
629 | |||
630 | match = re.match(date_regex, datestr) | ||
631 | if not match: | ||
632 | raise LLSDParseError("invalid date string '%s'." % datestr) | ||
633 | |||
634 | year = int(match.group('year')) | ||
635 | month = int(match.group('month')) | ||
636 | day = int(match.group('day')) | ||
637 | hour = int(match.group('hour')) | ||
638 | minute = int(match.group('minute')) | ||
639 | second = int(match.group('second')) | ||
640 | seconds_float = match.group('second_float') | ||
641 | microsecond = 0 | ||
642 | if seconds_float: | ||
643 | microsecond = int(seconds_float[1:]) * 10000 | ||
644 | return datetime.datetime(year, month, day, hour, minute, second, microsecond) | ||
645 | |||
646 | def _parse_real(self): | ||
647 | match = re.match(real_regex, self._buffer[self._index:]) | ||
648 | if not match: | ||
649 | raise LLSDParseError("invalid real token at index %d." % self._index) | ||
650 | |||
651 | (start, end) = match.span() | ||
652 | start += self._index | ||
653 | end += self._index | ||
654 | self._index = end | ||
655 | return float( self._buffer[start:end] ) | ||
656 | |||
657 | def _parse_integer(self): | ||
658 | match = re.match(int_regex, self._buffer[self._index:]) | ||
659 | if not match: | ||
660 | raise LLSDParseError("invalid integer token at index %d." % self._index) | ||
661 | |||
662 | (start, end) = match.span() | ||
663 | start += self._index | ||
664 | end += self._index | ||
665 | self._index = end | ||
666 | return int( self._buffer[start:end] ) | ||
667 | |||
668 | def _parse_string(self, delim): | ||
669 | """ string: "g'day" | 'have a "nice" day' | s(size)"raw data" """ | ||
670 | rv = "" | ||
671 | |||
672 | if delim in ("'", '"'): | ||
673 | rv = self._parse_string_delim(delim) | ||
674 | elif delim == 's': | ||
675 | rv = self._parse_string_raw() | ||
676 | else: | ||
677 | raise LLSDParseError("invalid string token at index %d." % self._index) | ||
678 | |||
679 | return rv | ||
680 | |||
681 | |||
682 | def _parse_string_delim(self, delim): | ||
683 | """ string: "g'day 'un" | 'have a "nice" day' """ | ||
684 | list = [] | ||
685 | found_escape = False | ||
686 | found_hex = False | ||
687 | found_digit = False | ||
688 | byte = 0 | ||
689 | while True: | ||
690 | cc = self._buffer[self._index] | ||
691 | self._index += 1 | ||
692 | if found_escape: | ||
693 | if found_hex: | ||
694 | if found_digit: | ||
695 | found_escape = False | ||
696 | found_hex = False | ||
697 | found_digit = False | ||
698 | byte <<= 4 | ||
699 | byte |= _hex_as_nybble(cc) | ||
700 | list.append(chr(byte)) | ||
701 | byte = 0 | ||
702 | else: | ||
703 | found_digit = True | ||
704 | byte = _hex_as_nybble(cc) | ||
705 | elif cc == 'x': | ||
706 | found_hex = True | ||
707 | else: | ||
708 | if cc == 'a': | ||
709 | list.append('\a') | ||
710 | elif cc == 'b': | ||
711 | list.append('\b') | ||
712 | elif cc == 'f': | ||
713 | list.append('\f') | ||
714 | elif cc == 'n': | ||
715 | list.append('\n') | ||
716 | elif cc == 'r': | ||
717 | list.append('\r') | ||
718 | elif cc == 't': | ||
719 | list.append('\t') | ||
720 | elif cc == 'v': | ||
721 | list.append('\v') | ||
722 | else: | ||
723 | list.append(cc) | ||
724 | found_escape = False | ||
725 | elif cc == '\\': | ||
726 | found_escape = True | ||
727 | elif cc == delim: | ||
728 | break | ||
729 | else: | ||
730 | list.append(cc) | ||
731 | return ''.join(list) | ||
732 | |||
733 | def _parse_string_raw(self): | ||
734 | """ string: s(size)"raw data" """ | ||
735 | # Read the (size) portion. | ||
736 | cc = self._buffer[self._index] | ||
737 | self._index += 1 | ||
738 | if cc != '(': | ||
739 | raise LLSDParseError("invalid string token at index %d." % self._index) | ||
740 | |||
741 | rparen = self._buffer.find(')', self._index) | ||
742 | if rparen == -1: | ||
743 | raise LLSDParseError("invalid string token at index %d." % self._index) | ||
744 | |||
745 | size = int(self._buffer[self._index:rparen]) | ||
746 | |||
747 | self._index = rparen + 1 | ||
748 | delim = self._buffer[self._index] | ||
749 | self._index += 1 | ||
750 | if delim not in ("'", '"'): | ||
751 | raise LLSDParseError("invalid string token at index %d." % self._index) | ||
752 | |||
753 | rv = self._buffer[self._index:(self._index + size)] | ||
754 | self._index += size | ||
755 | cc = self._buffer[self._index] | ||
756 | self._index += 1 | ||
757 | if cc != delim: | ||
758 | raise LLSDParseError("invalid string token at index %d." % self._index) | ||
759 | |||
760 | return rv | ||
761 | |||
762 | def format_binary(something): | ||
763 | return '<?llsd/binary?>\n' + _format_binary_recurse(something) | ||
764 | |||
765 | def _format_binary_recurse(something): | ||
766 | if something is None: | ||
767 | return '!' | ||
768 | elif isinstance(something, LLSD): | ||
769 | return _format_binary_recurse(something.thing) | ||
770 | elif isinstance(something, bool): | ||
771 | if something: | ||
772 | return '1' | ||
773 | else: | ||
774 | return '0' | ||
775 | elif isinstance(something, (int, long)): | ||
776 | return 'i' + struct.pack('!i', something) | ||
777 | elif isinstance(something, float): | ||
778 | return 'r' + struct.pack('!d', something) | ||
779 | elif isinstance(something, lluuid.UUID): | ||
780 | return 'u' + something._bits | ||
781 | elif isinstance(something, binary): | ||
782 | return 'b' + struct.pack('!i', len(something)) + something | ||
783 | elif isinstance(something, (str, unicode)): | ||
784 | return 's' + struct.pack('!i', len(something)) + something | ||
785 | elif isinstance(something, uri): | ||
786 | return 'l' + struct.pack('!i', len(something)) + something | ||
787 | elif isinstance(something, datetime.datetime): | ||
788 | seconds_since_epoch = time.mktime(something.timetuple()) | ||
789 | return 'd' + struct.pack('!d', seconds_since_epoch) | ||
790 | elif isinstance(something, (list, tuple)): | ||
791 | array_builder = [] | ||
792 | array_builder.append('[' + struct.pack('!i', len(something))) | ||
793 | for item in something: | ||
794 | array_builder.append(_format_binary_recurse(item)) | ||
795 | array_builder.append(']') | ||
796 | return ''.join(array_builder) | ||
797 | elif isinstance(something, dict): | ||
798 | map_builder = [] | ||
799 | map_builder.append('{' + struct.pack('!i', len(something))) | ||
800 | for key, value in something.items(): | ||
801 | map_builder.append('k' + struct.pack('!i', len(key)) + key) | ||
802 | map_builder.append(_format_binary_recurse(value)) | ||
803 | map_builder.append('}') | ||
804 | return ''.join(map_builder) | ||
805 | else: | ||
806 | raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( | ||
807 | type(something), something)) | ||
808 | |||
809 | |||
810 | def parse(something): | ||
811 | try: | ||
812 | if something.startswith('<?llsd/binary?>'): | ||
813 | just_binary = something.split('\n', 1)[1] | ||
814 | return LLSDBinaryParser().parse(just_binary) | ||
815 | # This should be better. | ||
816 | elif something.startswith('<'): | ||
817 | return to_python(fromstring(something)[0]) | ||
818 | else: | ||
819 | return LLSDNotationParser().parse(something) | ||
820 | except KeyError, e: | ||
821 | raise Exception('LLSD could not be parsed: %s' % (e,)) | ||
822 | |||
823 | class LLSD(object): | ||
824 | def __init__(self, thing=None): | ||
825 | self.thing = thing | ||
826 | |||
827 | def __str__(self): | ||
828 | return self.toXML(self.thing) | ||
829 | |||
830 | parse = staticmethod(parse) | ||
831 | toXML = staticmethod(format_xml) | ||
832 | toBinary = staticmethod(format_binary) | ||
833 | toNotation = staticmethod(format_notation) | ||
834 | |||
835 | |||
836 | undef = LLSD(None) | ||
837 | |||
838 | # register converters for stacked, if stacked is available | ||
839 | try: | ||
840 | from mulib import stacked | ||
841 | stacked.NoProducer() # just to exercise stacked | ||
842 | except: | ||
843 | print "Couldn't import mulib.stacked, not registering LLSD converters" | ||
844 | else: | ||
845 | def llsd_convert_json(llsd_stuff, request): | ||
846 | callback = request.get_header('callback') | ||
847 | if callback is not None: | ||
848 | ## See Yahoo's ajax documentation for information about using this | ||
849 | ## callback style of programming | ||
850 | ## http://developer.yahoo.com/common/json.html#callbackparam | ||
851 | req.write("%s(%s)" % (callback, simplejson.dumps(llsd_stuff))) | ||
852 | else: | ||
853 | req.write(simplejson.dumps(llsd_stuff)) | ||
854 | |||
855 | def llsd_convert_xml(llsd_stuff, request): | ||
856 | request.write(format_xml(llsd_stuff)) | ||
857 | |||
858 | def llsd_convert_binary(llsd_stuff, request): | ||
859 | request.write(format_binary(llsd_stuff)) | ||
860 | |||
861 | for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: | ||
862 | stacked.add_producer(typ, llsd_convert_json, 'application/json') | ||
863 | |||
864 | stacked.add_producer(typ, llsd_convert_xml, 'application/llsd+xml') | ||
865 | stacked.add_producer(typ, llsd_convert_xml, 'application/xml') | ||
866 | stacked.add_producer(typ, llsd_convert_xml, 'text/xml') | ||
867 | |||
868 | stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') | ||
869 | |||
870 | stacked.add_producer(LLSD, llsd_convert_xml, '*/*') | ||
diff --git a/linden/indra/lib/python/indra/base/lluuid.py b/linden/indra/lib/python/indra/base/lluuid.py new file mode 100644 index 0000000..f302f8b --- /dev/null +++ b/linden/indra/lib/python/indra/base/lluuid.py | |||
@@ -0,0 +1,290 @@ | |||
1 | """\ | ||
2 | @file lluuid.py | ||
3 | @brief UUID parser/generator. | ||
4 | |||
5 | $LicenseInfo:firstyear=2004&license=mit$ | ||
6 | |||
7 | Copyright (c) 2004-2007, 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 | import md5, random, socket, string, time, re | ||
30 | |||
31 | def _int2binstr(i,l): | ||
32 | s='' | ||
33 | for a in range(l): | ||
34 | s=chr(i&0xFF)+s | ||
35 | i>>=8 | ||
36 | return s | ||
37 | |||
38 | def _binstr2int(s): | ||
39 | i = long(0) | ||
40 | for c in s: | ||
41 | i = (i<<8) + ord(c) | ||
42 | return i | ||
43 | |||
44 | class UUID(object): | ||
45 | """ | ||
46 | A class which represents a 16 byte integer. Stored as a 16 byte 8 | ||
47 | bit character string. | ||
48 | |||
49 | The string version is to be of the form: | ||
50 | AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) | ||
51 | where A=network address, B=timestamp, C=random. | ||
52 | """ | ||
53 | |||
54 | NULL_STR = "00000000-0000-0000-0000-000000000000" | ||
55 | |||
56 | # the UUIDREGEX_STRING is helpful for parsing UUID's in text | ||
57 | hex_wildcard = r"[0-9a-fA-F]" | ||
58 | word = hex_wildcard + r"{4,4}-" | ||
59 | long_word = hex_wildcard + r"{8,8}-" | ||
60 | very_long_word = hex_wildcard + r"{12,12}" | ||
61 | UUID_REGEX_STRING = long_word + word + word + word + very_long_word | ||
62 | uuid_regex = re.compile(UUID_REGEX_STRING) | ||
63 | |||
64 | rand = random.Random() | ||
65 | ip = '' | ||
66 | try: | ||
67 | ip = socket.gethostbyname(socket.gethostname()) | ||
68 | except(socket.gaierror): | ||
69 | # no ip address, so just default to somewhere in 10.x.x.x | ||
70 | ip = '10' | ||
71 | for i in range(3): | ||
72 | ip += '.' + str(rand.randrange(1,254)) | ||
73 | hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) | ||
74 | lastid = '' | ||
75 | |||
76 | def __init__(self, string_with_uuid=None): | ||
77 | """ | ||
78 | Initialize to first valid UUID in string argument, | ||
79 | or to null UUID if none found or string is not supplied. | ||
80 | """ | ||
81 | self._bits = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
82 | if string_with_uuid: | ||
83 | uuid_match = UUID.uuid_regex.search(string_with_uuid) | ||
84 | if uuid_match: | ||
85 | uuid_string = uuid_match.group() | ||
86 | s = string.replace(uuid_string, '-', '') | ||
87 | self._bits = _int2binstr(string.atol(s[:8],16),4) + \ | ||
88 | _int2binstr(string.atol(s[8:16],16),4) + \ | ||
89 | _int2binstr(string.atol(s[16:24],16),4) + \ | ||
90 | _int2binstr(string.atol(s[24:],16),4) | ||
91 | |||
92 | def __len__(self): | ||
93 | """ | ||
94 | Used by the len() builtin. | ||
95 | """ | ||
96 | return 36 | ||
97 | |||
98 | def __nonzero__(self): | ||
99 | return self._bits != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" | ||
100 | |||
101 | def __str__(self): | ||
102 | uuid_string = self.toString() | ||
103 | return uuid_string | ||
104 | |||
105 | __repr__ = __str__ | ||
106 | |||
107 | def __getitem__(self, index): | ||
108 | return str(self)[index] | ||
109 | |||
110 | def __eq__(self, other): | ||
111 | if isinstance(other, (str, unicode)): | ||
112 | return other == str(self) | ||
113 | return self._bits == getattr(other, '_bits', '') | ||
114 | |||
115 | def __ne__(self, other): | ||
116 | return not self.__eq__(other) | ||
117 | |||
118 | def __le__(self, other): | ||
119 | return self._bits <= other._bits | ||
120 | |||
121 | def __ge__(self, other): | ||
122 | return self._bits >= other._bits | ||
123 | |||
124 | def __lt__(self, other): | ||
125 | return self._bits < other._bits | ||
126 | |||
127 | def __gt__(self, other): | ||
128 | return self._bits > other._bits | ||
129 | |||
130 | def __hash__(self): | ||
131 | return hash(self._bits) | ||
132 | |||
133 | def set(self, uuid): | ||
134 | self._bits = uuid._bits | ||
135 | |||
136 | def setFromString(self, uuid_string): | ||
137 | """ | ||
138 | Given a string version of a uuid, set self bits | ||
139 | appropriately. Returns self. | ||
140 | """ | ||
141 | s = string.replace(uuid_string, '-', '') | ||
142 | self._bits = _int2binstr(string.atol(s[:8],16),4) + \ | ||
143 | _int2binstr(string.atol(s[8:16],16),4) + \ | ||
144 | _int2binstr(string.atol(s[16:24],16),4) + \ | ||
145 | _int2binstr(string.atol(s[24:],16),4) | ||
146 | return self | ||
147 | |||
148 | def setFromMemoryDump(self, gdb_string): | ||
149 | """ | ||
150 | We expect to get gdb_string as four hex units. eg: | ||
151 | 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 | ||
152 | Which will be translated to: | ||
153 | db547d14-1b3f4bc3-9b984f71-d22f890a | ||
154 | Returns self. | ||
155 | """ | ||
156 | s = string.replace(gdb_string, '0x', '') | ||
157 | s = string.replace(s, ' ', '') | ||
158 | t = '' | ||
159 | for i in range(8,40,8): | ||
160 | for j in range(0,8,2): | ||
161 | t = t + s[i-j-2:i-j] | ||
162 | self.setFromString(t) | ||
163 | |||
164 | def toString(self): | ||
165 | """ | ||
166 | Return as a string matching the LL standard | ||
167 | AAAAAAAA-AAAA-BBBB-BBBB-BBBBBBCCCCCC (a 128-bit number in hex) | ||
168 | where A=network address, B=timestamp, C=random. | ||
169 | """ | ||
170 | return uuid_bits_to_string(self._bits) | ||
171 | |||
172 | def getAsString(self): | ||
173 | """ | ||
174 | Return a different string representation of the form | ||
175 | AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC (a 128-bit number in hex) | ||
176 | where A=network address, B=timestamp, C=random. | ||
177 | """ | ||
178 | i1 = _binstr2int(self._bits[0:4]) | ||
179 | i2 = _binstr2int(self._bits[4:8]) | ||
180 | i3 = _binstr2int(self._bits[8:12]) | ||
181 | i4 = _binstr2int(self._bits[12:16]) | ||
182 | return '%08lx-%08lx-%08lx-%08lx' % (i1,i2,i3,i4) | ||
183 | |||
184 | def generate(self): | ||
185 | """ | ||
186 | Generate a new uuid. This algorithm is slightly different | ||
187 | from c++ implementation for portability reasons. | ||
188 | Returns self. | ||
189 | """ | ||
190 | newid = self.__class__.lastid | ||
191 | while newid == self.__class__.lastid: | ||
192 | now = long(time.time() * 1000) | ||
193 | newid = ("%016x" % now) + self.__class__.hexip | ||
194 | newid += ("%03x" % (self.__class__.rand.randrange(0,4095))) | ||
195 | self.__class__.lastid = newid | ||
196 | m = md5.new() | ||
197 | m.update(newid) | ||
198 | self._bits = m.digest() | ||
199 | return self | ||
200 | |||
201 | def isNull(self): | ||
202 | """ | ||
203 | Returns 1 if the uuid is null - ie, equal to default uuid. | ||
204 | """ | ||
205 | return (self._bits == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") | ||
206 | |||
207 | def xor(self, rhs): | ||
208 | """ | ||
209 | xors self with rhs. | ||
210 | """ | ||
211 | v1 = _binstr2int(self._bits[0:4]) ^ _binstr2int(rhs._bits[0:4]) | ||
212 | v2 = _binstr2int(self._bits[4:8]) ^ _binstr2int(rhs._bits[4:8]) | ||
213 | v3 = _binstr2int(self._bits[8:12]) ^ _binstr2int(rhs._bits[8:12]) | ||
214 | v4 = _binstr2int(self._bits[12:16]) ^ _binstr2int(rhs._bits[12:16]) | ||
215 | self._bits = _int2binstr(v1,4) + \ | ||
216 | _int2binstr(v2,4) + \ | ||
217 | _int2binstr(v3,4) + \ | ||
218 | _int2binstr(v4,4) | ||
219 | |||
220 | def printTranslatedMemory(four_hex_uints): | ||
221 | """ | ||
222 | We expect to get the string as four hex units. eg: | ||
223 | 0x147d54db 0xc34b3f1b 0x714f989b 0x0a892fd2 | ||
224 | Which will be translated to: | ||
225 | db547d14-1b3f4bc3-9b984f71-d22f890a | ||
226 | """ | ||
227 | uuid = UUID() | ||
228 | uuid.setFromMemoryDump(four_hex_uints) | ||
229 | print uuid.toString() | ||
230 | |||
231 | def isPossiblyID(id_str): | ||
232 | """ | ||
233 | This function returns 1 if the string passed has some uuid-like | ||
234 | characteristics. Otherwise returns 0. | ||
235 | """ | ||
236 | if not id_str or len(id_str) < 5 or len(id_str) > 36: | ||
237 | return 0 | ||
238 | |||
239 | if isinstance(id_str, UUID) or UUID.uuid_regex.match(id_str): | ||
240 | return 1 | ||
241 | # build a string which matches every character. | ||
242 | hex_wildcard = r"[0-9a-fA-F]" | ||
243 | chars = len(id_str) | ||
244 | next = min(chars, 8) | ||
245 | matcher = hex_wildcard+"{"+str(next)+","+str(next)+"}" | ||
246 | chars = chars - next | ||
247 | if chars > 0: | ||
248 | matcher = matcher + "-" | ||
249 | chars = chars - 1 | ||
250 | for block in range(3): | ||
251 | next = max(min(chars, 4), 0) | ||
252 | if next: | ||
253 | matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" | ||
254 | chars = chars - next | ||
255 | if chars > 0: | ||
256 | matcher = matcher + "-" | ||
257 | chars = chars - 1 | ||
258 | if chars > 0: | ||
259 | next = min(chars, 12) | ||
260 | matcher = matcher + hex_wildcard+"{"+str(next)+","+str(next)+"}" | ||
261 | #print matcher | ||
262 | uuid_matcher = re.compile(matcher) | ||
263 | if uuid_matcher.match(id_str): | ||
264 | return 1 | ||
265 | return 0 | ||
266 | |||
267 | def uuid_bits_to_string(bits): | ||
268 | i1 = _binstr2int(bits[0:4]) | ||
269 | i2 = _binstr2int(bits[4:6]) | ||
270 | i3 = _binstr2int(bits[6:8]) | ||
271 | i4 = _binstr2int(bits[8:10]) | ||
272 | i5 = _binstr2int(bits[10:12]) | ||
273 | i6 = _binstr2int(bits[12:16]) | ||
274 | return '%08lx-%04lx-%04lx-%04lx-%04lx%08lx' % (i1,i2,i3,i4,i5,i6) | ||
275 | |||
276 | def uuid_bits_to_uuid(bits): | ||
277 | return UUID(uuid_bits_to_string(bits)) | ||
278 | |||
279 | |||
280 | try: | ||
281 | from mulib import stacked | ||
282 | stacked.NoProducer() # just to exercise stacked | ||
283 | except: | ||
284 | print "Couldn't import mulib.stacked, not registering UUID converter" | ||
285 | else: | ||
286 | def convertUUID(uuid, req): | ||
287 | req.write(str(uuid)) | ||
288 | |||
289 | stacked.add_producer(UUID, convertUUID, "*/*") | ||
290 | stacked.add_producer(UUID, convertUUID, "text/html") | ||
diff --git a/linden/indra/lib/python/indra/ipc/__init__.py b/linden/indra/lib/python/indra/ipc/__init__.py index 92c9416..4395361 100644 --- a/linden/indra/lib/python/indra/ipc/__init__.py +++ b/linden/indra/lib/python/indra/ipc/__init__.py | |||
@@ -2,26 +2,26 @@ | |||
2 | @file __init__.py | 2 | @file __init__.py |
3 | @brief Initialization file for the indra ipc module. | 3 | @brief Initialization file for the indra ipc module. |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
5 | Copyright (c) 2006-2007, Linden Research, Inc. | 7 | Copyright (c) 2006-2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | of this software and associated documentation files (the "Software"), to deal |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | in the Software without restriction, including without limitation the rights |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | copies of the Software, and to permit persons to whom the Software is |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | furnished to do so, subject to the following conditions: |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | The above copyright notice and this permission notice shall be included in |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | all copies or substantial portions of the Software. |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | # online at http://secondlife.com/developers/opensource/flossexception | 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | # | 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | # By copying, modifying or distributing this software, you acknowledge | 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | # that you have read and understood your obligations described above, | 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | # and agree to abide by those obligations. | 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | # | 25 | THE SOFTWARE. |
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 26 | $/LicenseInfo$ |
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 27 | """ |
diff --git a/linden/indra/lib/python/indra/ipc/compatibility.py b/linden/indra/lib/python/indra/ipc/compatibility.py index 358820c..8435528 100644 --- a/linden/indra/lib/python/indra/ipc/compatibility.py +++ b/linden/indra/lib/python/indra/ipc/compatibility.py | |||
@@ -2,28 +2,28 @@ | |||
2 | @file compatibility.py | 2 | @file compatibility.py |
3 | @brief Classes that manage compatibility states. | 3 | @brief Classes that manage compatibility states. |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
5 | Copyright (c) 2007, Linden Research, Inc. | 7 | Copyright (c) 2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | of this software and associated documentation files (the "Software"), to deal |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | in the Software without restriction, including without limitation the rights |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | copies of the Software, and to permit persons to whom the Software is |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | furnished to do so, subject to the following conditions: |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | The above copyright notice and this permission notice shall be included in |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | all copies or substantial portions of the Software. |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | # online at http://secondlife.com/developers/opensource/flossexception | 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | # | 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | # By copying, modifying or distributing this software, you acknowledge | 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | # that you have read and understood your obligations described above, | 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | # and agree to abide by those obligations. | 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | # | 25 | THE SOFTWARE. |
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 26 | $/LicenseInfo$ |
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 27 | """ |
28 | 28 | ||
29 | 29 | ||
diff --git a/linden/indra/lib/python/indra/ipc/httputil.py b/linden/indra/lib/python/indra/ipc/httputil.py new file mode 100644 index 0000000..c4ac0a3 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/httputil.py | |||
@@ -0,0 +1,9 @@ | |||
1 | |||
2 | import warnings | ||
3 | |||
4 | warnings.warn("indra.ipc.httputil has been deprecated; use eventlet.httpc instead", DeprecationWarning, 2) | ||
5 | |||
6 | from eventlet.httpc import * | ||
7 | |||
8 | |||
9 | makeConnection = make_connection | ||
diff --git a/linden/indra/lib/python/indra/ipc/llmessage.py b/linden/indra/lib/python/indra/ipc/llmessage.py index de6fd3b..2497393 100644 --- a/linden/indra/lib/python/indra/ipc/llmessage.py +++ b/linden/indra/lib/python/indra/ipc/llmessage.py | |||
@@ -2,28 +2,28 @@ | |||
2 | @file llmessage.py | 2 | @file llmessage.py |
3 | @brief Message template parsing and compatiblity | 3 | @brief Message template parsing and compatiblity |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
5 | Copyright (c) 2007, Linden Research, Inc. | 7 | Copyright (c) 2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | of this software and associated documentation files (the "Software"), to deal |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | in the Software without restriction, including without limitation the rights |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | copies of the Software, and to permit persons to whom the Software is |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | furnished to do so, subject to the following conditions: |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | The above copyright notice and this permission notice shall be included in |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | all copies or substantial portions of the Software. |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | # online at http://secondlife.com/developers/opensource/flossexception | 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | # | 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | # By copying, modifying or distributing this software, you acknowledge | 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | # that you have read and understood your obligations described above, | 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | # and agree to abide by those obligations. | 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | # | 25 | THE SOFTWARE. |
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 26 | $/LicenseInfo$ |
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 27 | """ |
28 | 28 | ||
29 | from sets import Set, ImmutableSet | 29 | from sets import Set, ImmutableSet |
diff --git a/linden/indra/lib/python/indra/ipc/llsdhttp.py b/linden/indra/lib/python/indra/ipc/llsdhttp.py new file mode 100644 index 0000000..0d1a1c6 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/llsdhttp.py | |||
@@ -0,0 +1,84 @@ | |||
1 | """\ | ||
2 | @file llsdhttp.py | ||
3 | @brief Functions to ease moving llsd over http | ||
4 | |||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
7 | Copyright (c) 2006-2007, 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 | import os.path | ||
30 | import os | ||
31 | import urlparse | ||
32 | |||
33 | from indra.base import llsd | ||
34 | |||
35 | from eventlet import httpc | ||
36 | |||
37 | |||
38 | get, put, delete, post = httpc.make_suite( | ||
39 | llsd.format_xml, llsd.parse, 'application/xml+llsd') | ||
40 | |||
41 | |||
42 | for x in (httpc.ConnectionError, httpc.NotFound, httpc.Forbidden): | ||
43 | globals()[x.__name__] = x | ||
44 | |||
45 | |||
46 | def postFile(url, filename, verbose=False): | ||
47 | f = open(filename) | ||
48 | body = f.read() | ||
49 | f.close() | ||
50 | llsd_body = llsd.parse(body) | ||
51 | return post(url, llsd_body, verbose=verbose) | ||
52 | |||
53 | |||
54 | def getStatus(url, use_proxy=False): | ||
55 | status, _headers, _body = get(url, use_proxy=use_proxy, verbose=True) | ||
56 | return status | ||
57 | |||
58 | |||
59 | def putStatus(url, data): | ||
60 | status, _headers, _body = put(url, data, verbose=True) | ||
61 | return status | ||
62 | |||
63 | |||
64 | def deleteStatus(url): | ||
65 | status, _headers, _body = delete(url, verbose=True) | ||
66 | return status | ||
67 | |||
68 | |||
69 | def postStatus(url, data): | ||
70 | status, _headers, _body = post(url, data, verbose=True) | ||
71 | return status | ||
72 | |||
73 | |||
74 | def postFileStatus(url, filename): | ||
75 | status, _headers, body = postFile(url, filename, verbose=True) | ||
76 | return status, body | ||
77 | |||
78 | |||
79 | def getFromSimulator(path, use_proxy=False): | ||
80 | return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy) | ||
81 | |||
82 | |||
83 | def postToSimulator(path, data=None): | ||
84 | return post('http://' + simulatorHostAndPort + path, data) | ||
diff --git a/linden/indra/lib/python/indra/ipc/mysql_pool.py b/linden/indra/lib/python/indra/ipc/mysql_pool.py new file mode 100644 index 0000000..4a265a1 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/mysql_pool.py | |||
@@ -0,0 +1,103 @@ | |||
1 | """\ | ||
2 | @file mysql_pool.py | ||
3 | @brief Uses saranwrap to implement a pool of nonblocking database connections to a mysql server. | ||
4 | |||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
7 | Copyright (c) 2007, 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 | 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 | ||
36 | |||
37 | # method 2: better -- admits the existence of the pool | ||
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): | ||
50 | """\ | ||
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 | |||
67 | def get(self, host, dbname): | ||
68 | key = (host, dbname) | ||
69 | if key not in self._databases: | ||
70 | new_kwargs = self._kwargs.copy() | ||
71 | new_kwargs['db'] = dbname | ||
72 | new_kwargs['host'] = host | ||
73 | new_kwargs.update(self.credentials_for(host)) | ||
74 | dbpool = ConnectionPool(self._min_size, self._max_size, *self._args, **new_kwargs) | ||
75 | self._databases[key] = dbpool | ||
76 | |||
77 | return self._databases[key] | ||
78 | |||
79 | |||
80 | class ConnectionPool(Pool): | ||
81 | """A pool which gives out saranwrapped MySQLdb connections from a pool | ||
82 | """ | ||
83 | def __init__(self, min_size = 0, max_size = 4, *args, **kwargs): | ||
84 | self._args = args | ||
85 | self._kwargs = kwargs | ||
86 | Pool.__init__(self, min_size, max_size) | ||
87 | |||
88 | def create(self): | ||
89 | return saranwrap.wrap(MySQLdb).connect(*self._args, **self._kwargs) | ||
90 | |||
91 | def put(self, conn): | ||
92 | # rollback any uncommitted changes, so that the next process | ||
93 | # has a clean slate. This also pokes the process to see if | ||
94 | # it's dead or None | ||
95 | try: | ||
96 | conn.rollback() | ||
97 | except (AttributeError, DeadProcess), e: | ||
98 | conn = self.create() | ||
99 | # TODO figure out if we're still connected to the database | ||
100 | if conn: | ||
101 | Pool.put(self, conn) | ||
102 | else: | ||
103 | self.current_size -= 1 | ||
diff --git a/linden/indra/lib/python/indra/ipc/russ.py b/linden/indra/lib/python/indra/ipc/russ.py new file mode 100644 index 0000000..1ef5562 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/russ.py | |||
@@ -0,0 +1,157 @@ | |||
1 | """\ | ||
2 | @file russ.py | ||
3 | @brief Recursive URL Substitution Syntax helpers | ||
4 | @author Phoenix | ||
5 | |||
6 | Many details on how this should work is available on the wiki: | ||
7 | https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax | ||
8 | |||
9 | Adding features to this should be reflected in that page in the | ||
10 | implementations section. | ||
11 | |||
12 | $LicenseInfo:firstyear=2007&license=mit$ | ||
13 | |||
14 | Copyright (c) 2007, Linden Research, Inc. | ||
15 | |||
16 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
17 | of this software and associated documentation files (the "Software"), to deal | ||
18 | in the Software without restriction, including without limitation the rights | ||
19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
20 | copies of the Software, and to permit persons to whom the Software is | ||
21 | furnished to do so, subject to the following conditions: | ||
22 | |||
23 | The above copyright notice and this permission notice shall be included in | ||
24 | all copies or substantial portions of the Software. | ||
25 | |||
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
32 | THE SOFTWARE. | ||
33 | $/LicenseInfo$ | ||
34 | """ | ||
35 | |||
36 | import urllib | ||
37 | from indra.ipc import llsdhttp | ||
38 | |||
39 | class UnbalancedBraces(Exception): | ||
40 | pass | ||
41 | |||
42 | class UnknownDirective(Exception): | ||
43 | pass | ||
44 | |||
45 | class BadDirective(Exception): | ||
46 | pass | ||
47 | |||
48 | def format_value_for_path(value): | ||
49 | if type(value) in [list, tuple]: | ||
50 | # *NOTE: treat lists as unquoted path components so that the quoting | ||
51 | # doesn't get out-of-hand. This is a workaround for the fact that | ||
52 | # russ always quotes, even if the data it's given is already quoted, | ||
53 | # and it's not safe to simply unquote a path directly, so if we want | ||
54 | # russ to substitute urls parts inside other url parts we always | ||
55 | # have to do so via lists of unquoted path components. | ||
56 | return '/'.join([urllib.quote(str(item)) for item in value]) | ||
57 | else: | ||
58 | return urllib.quote(str(value)) | ||
59 | |||
60 | def format(format_str, context): | ||
61 | """@brief Format format string according to rules for RUSS. | ||
62 | @see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax | ||
63 | @param format_str The input string to format. | ||
64 | @param context A map used for string substitutions. | ||
65 | @return Returns the formatted string. If no match, the braces remain intact. | ||
66 | """ | ||
67 | while True: | ||
68 | #print "format_str:", format_str | ||
69 | all_matches = _find_sub_matches(format_str) | ||
70 | if not all_matches: | ||
71 | break | ||
72 | substitutions = 0 | ||
73 | while True: | ||
74 | matches = all_matches.pop() | ||
75 | # we work from right to left to make sure we do not | ||
76 | # invalidate positions earlier in format_str | ||
77 | matches.reverse() | ||
78 | for pos in matches: | ||
79 | # Use index since _find_sub_matches should have raised | ||
80 | # an exception, and failure to find now is an exception. | ||
81 | end = format_str.index('}', pos) | ||
82 | #print "directive:", format_str[pos+1:pos+5] | ||
83 | if format_str[pos + 1] == '$': | ||
84 | value = context[format_str[pos + 2:end]] | ||
85 | if value is not None: | ||
86 | value = format_value_for_path(value) | ||
87 | elif format_str[pos + 1] == '%': | ||
88 | value = _build_query_string( | ||
89 | context.get(format_str[pos + 2:end])) | ||
90 | elif format_str[pos+1:pos+5] == 'http' or format_str[pos+1:pos+5] == 'file': | ||
91 | value = _fetch_url_directive(format_str[pos + 1:end]) | ||
92 | else: | ||
93 | raise UnknownDirective, format_str[pos:end + 1] | ||
94 | if value is not None: | ||
95 | format_str = format_str[:pos]+str(value)+format_str[end+1:] | ||
96 | substitutions += 1 | ||
97 | |||
98 | # If there were any substitutions at this depth, re-parse | ||
99 | # since this may have revealed new things to substitute | ||
100 | if substitutions: | ||
101 | break | ||
102 | if not all_matches: | ||
103 | break | ||
104 | |||
105 | # If there were no substitutions at all, and we have exhausted | ||
106 | # the possible matches, bail. | ||
107 | if not substitutions: | ||
108 | break | ||
109 | return format_str | ||
110 | |||
111 | def _find_sub_matches(format_str): | ||
112 | """@brief Find all of the substitution matches. | ||
113 | @param format_str the RUSS conformant format string. | ||
114 | @return Returns an array of depths of arrays of positional matches in input. | ||
115 | """ | ||
116 | depth = 0 | ||
117 | matches = [] | ||
118 | for pos in range(len(format_str)): | ||
119 | if format_str[pos] == '{': | ||
120 | depth += 1 | ||
121 | if not len(matches) == depth: | ||
122 | matches.append([]) | ||
123 | matches[depth - 1].append(pos) | ||
124 | continue | ||
125 | if format_str[pos] == '}': | ||
126 | depth -= 1 | ||
127 | continue | ||
128 | if not depth == 0: | ||
129 | raise UnbalancedBraces, format_str | ||
130 | return matches | ||
131 | |||
132 | def _build_query_string(query_dict): | ||
133 | """\ | ||
134 | @breif given a dict, return a query string. utility wrapper for urllib. | ||
135 | @param query_dict input query dict | ||
136 | @returns Returns an urlencoded query string including leading '?'. | ||
137 | """ | ||
138 | if query_dict: | ||
139 | return '?' + urllib.urlencode(query_dict) | ||
140 | else: | ||
141 | return '' | ||
142 | |||
143 | def _fetch_url_directive(directive): | ||
144 | "*FIX: This only supports GET" | ||
145 | commands = directive.split('|') | ||
146 | resource = llsdhttp.get(commands[0]) | ||
147 | if len(commands) == 3: | ||
148 | resource = _walk_resource(resource, commands[2]) | ||
149 | return resource | ||
150 | |||
151 | def _walk_resource(resource, path): | ||
152 | path = path.split('/') | ||
153 | for child in path: | ||
154 | if not child: | ||
155 | continue | ||
156 | resource = resource[child] | ||
157 | return resource | ||
diff --git a/linden/indra/lib/python/indra/ipc/saranwrap.py b/linden/indra/lib/python/indra/ipc/saranwrap.py new file mode 100644 index 0000000..31705c4 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/saranwrap.py | |||
@@ -0,0 +1,637 @@ | |||
1 | """\ | ||
2 | @file saranwrap.py | ||
3 | @author Phoenix | ||
4 | @date 2007-07-13 | ||
5 | @brief A simple, pickle based rpc mechanism which reflects python | ||
6 | objects and callables. | ||
7 | |||
8 | $LicenseInfo:firstyear=2007&license=mit$ | ||
9 | |||
10 | Copyright (c) 2007, Linden Research, Inc. | ||
11 | |||
12 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
13 | of this software and associated documentation files (the "Software"), to deal | ||
14 | in the Software without restriction, including without limitation the rights | ||
15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
16 | copies of the Software, and to permit persons to whom the Software is | ||
17 | furnished to do so, subject to the following conditions: | ||
18 | |||
19 | The above copyright notice and this permission notice shall be included in | ||
20 | all copies or substantial portions of the Software. | ||
21 | |||
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
28 | THE SOFTWARE. | ||
29 | $/LicenseInfo$ | ||
30 | |||
31 | This file provides classes and exceptions used for simple python level | ||
32 | remote procedure calls. This is achieved by intercepting the basic | ||
33 | getattr and setattr calls in a client proxy, which commnicates those | ||
34 | down to the server which will dispatch them to objects in it's process | ||
35 | space. | ||
36 | |||
37 | The basic protocol to get and set attributes is for the client proxy | ||
38 | to issue the command: | ||
39 | |||
40 | getattr $id $name | ||
41 | setattr $id $name $value | ||
42 | |||
43 | getitem $id $item | ||
44 | setitem $id $item $value | ||
45 | eq $id $rhs | ||
46 | del $id | ||
47 | |||
48 | When the get returns a callable, the client proxy will provide a | ||
49 | callable proxy which will invoke a remote procedure call. The command | ||
50 | issued from the callable proxy to server is: | ||
51 | |||
52 | call $id $name $args $kwargs | ||
53 | |||
54 | If the client supplies an id of None, then the get/set/call is applied | ||
55 | to the object(s) exported from the server. | ||
56 | |||
57 | The server will parse the get/set/call, take the action indicated, and | ||
58 | return back to the caller one of: | ||
59 | |||
60 | value $val | ||
61 | callable | ||
62 | object $id | ||
63 | exception $excp | ||
64 | |||
65 | To handle object expiration, the proxy will instruct the rpc server to | ||
66 | discard objects which are no longer in use. This is handled by | ||
67 | catching proxy deletion and sending the command: | ||
68 | |||
69 | del $id | ||
70 | |||
71 | The server will handle this by removing clearing it's own internal | ||
72 | references. This does not mean that the object will necessarily be | ||
73 | cleaned from the server, but no artificial references will remain | ||
74 | after successfully completing. On completion, the server will return | ||
75 | one of: | ||
76 | |||
77 | value None | ||
78 | exception $excp | ||
79 | |||
80 | The server also accepts a special command for debugging purposes: | ||
81 | |||
82 | status | ||
83 | |||
84 | Which will be intercepted by the server to write back: | ||
85 | |||
86 | status {...} | ||
87 | |||
88 | The wire protocol is to pickle the Request class in this file. The | ||
89 | request class is basically an action and a map of parameters' | ||
90 | """ | ||
91 | |||
92 | import os | ||
93 | import cPickle | ||
94 | import struct | ||
95 | import sys | ||
96 | |||
97 | try: | ||
98 | set = set | ||
99 | frozenset = frozenset | ||
100 | except NameError: | ||
101 | from sets import Set as set, ImmutableSet as frozenset | ||
102 | |||
103 | from eventlet.processes import Process | ||
104 | from eventlet import api | ||
105 | |||
106 | # | ||
107 | # debugging hooks | ||
108 | # | ||
109 | _g_debug_mode = False | ||
110 | if _g_debug_mode: | ||
111 | import traceback | ||
112 | |||
113 | |||
114 | def wrap(obj, dead_callback = None): | ||
115 | """ | ||
116 | @brief wrap in object in another process through a saranwrap proxy | ||
117 | @param object The object to wrap. | ||
118 | @param dead_callback A callable to invoke if the process exits.""" | ||
119 | |||
120 | if type(obj).__name__ == 'module': | ||
121 | return wrap_module(obj.__name__, dead_callback) | ||
122 | p = Process('python', [__file__, '--child'], dead_callback) | ||
123 | prox = Proxy(p, p) | ||
124 | prox.obj = obj | ||
125 | return prox.obj | ||
126 | |||
127 | def wrap_module(fqname, dead_callback = None): | ||
128 | """ | ||
129 | @brief wrap a module in another process through a saranwrap proxy | ||
130 | @param fqname The fully qualified name of the module. | ||
131 | @param dead_callback A callable to invoke if the process exits.""" | ||
132 | global _g_debug_mode | ||
133 | if _g_debug_mode: | ||
134 | p = Process('python', [__file__, '--module', fqname, '--logfile', '/tmp/saranwrap.log'], dead_callback) | ||
135 | else: | ||
136 | p = Process('python', [__file__, '--module', fqname,], dead_callback) | ||
137 | prox = Proxy(p, p) | ||
138 | return prox | ||
139 | |||
140 | def status(proxy): | ||
141 | """ | ||
142 | @brief get the status from the server through a proxy | ||
143 | @param proxy a saranwrap.Proxy object connected to a server.""" | ||
144 | _write_request(Request('status', {}), proxy.__local_dict['_out']) | ||
145 | return _read_response(None, None, proxy.__local_dict['_in'], proxy.__local_dict['_out'], None) | ||
146 | |||
147 | class BadResponse(Exception): | ||
148 | """"This exception is raised by an saranwrap client when it could | ||
149 | parse but cannot understand the response from the server.""" | ||
150 | pass | ||
151 | |||
152 | class BadRequest(Exception): | ||
153 | """"This exception is raised by a saranwrap server when it could parse | ||
154 | but cannot understand the response from the server.""" | ||
155 | pass | ||
156 | |||
157 | class UnrecoverableError(Exception): | ||
158 | pass | ||
159 | |||
160 | class Request(object): | ||
161 | "@brief A wrapper class for proxy requests to the server." | ||
162 | def __init__(self, action, param): | ||
163 | self._action = action | ||
164 | self._param = param | ||
165 | def __str__(self): | ||
166 | return "Request `"+self._action+"` "+str(self._param) | ||
167 | def __getitem__(self, name): | ||
168 | return self._param[name] | ||
169 | def action(self): | ||
170 | return self._action | ||
171 | |||
172 | def _read_lp_hunk(stream): | ||
173 | len_bytes = stream.read(4) | ||
174 | length = struct.unpack('I', len_bytes)[0] | ||
175 | body = stream.read(length) | ||
176 | return body | ||
177 | |||
178 | def _read_response(id, attribute, input, output, dead_list): | ||
179 | """@brief local helper method to read respones from the rpc server.""" | ||
180 | try: | ||
181 | str = _read_lp_hunk(input) | ||
182 | _prnt(`str`) | ||
183 | response = cPickle.loads(str) | ||
184 | except AttributeError, e: | ||
185 | raise UnrecoverableError(e) | ||
186 | _prnt("response: %s" % response) | ||
187 | if response[0] == 'value': | ||
188 | return response[1] | ||
189 | elif response[0] == 'callable': | ||
190 | return CallableProxy(id, attribute, input, output, dead_list) | ||
191 | elif response[0] == 'object': | ||
192 | return ObjectProxy(input, output, response[1], dead_list) | ||
193 | elif response[0] == 'exception': | ||
194 | exp = response[1] | ||
195 | raise exp | ||
196 | else: | ||
197 | raise BadResponse(response[0]) | ||
198 | |||
199 | def _write_lp_hunk(stream, hunk): | ||
200 | write_length = struct.pack('I', len(hunk)) | ||
201 | stream.write(write_length + hunk) | ||
202 | if hasattr(stream, 'flush'): | ||
203 | stream.flush() | ||
204 | |||
205 | def _write_request(param, output): | ||
206 | _prnt("request: %s" % param) | ||
207 | str = cPickle.dumps(param) | ||
208 | _write_lp_hunk(output, str) | ||
209 | |||
210 | def _is_local(attribute): | ||
211 | "Return true if the attribute should be handled locally" | ||
212 | # return attribute in ('_in', '_out', '_id', '__getattribute__', '__setattr__', '__dict__') | ||
213 | # good enough for now. :) | ||
214 | if '__local_dict' in attribute: | ||
215 | return True | ||
216 | return False | ||
217 | |||
218 | def _prnt(message): | ||
219 | global _g_debug_mode | ||
220 | if _g_debug_mode: | ||
221 | print message | ||
222 | |||
223 | _g_logfile = None | ||
224 | def _log(message): | ||
225 | global _g_logfile | ||
226 | if _g_logfile: | ||
227 | _g_logfile.write(str(os.getpid()) + ' ' + message) | ||
228 | _g_logfile.write('\n') | ||
229 | _g_logfile.flush() | ||
230 | |||
231 | def _unmunge_attr_name(name): | ||
232 | """ Sometimes attribute names come in with classname prepended, not sure why. | ||
233 | This function removes said classname, because we're huge hackers and we didn't | ||
234 | find out what the true right thing to do is. *FIX: find out. """ | ||
235 | if(name.startswith('_Proxy')): | ||
236 | name = name[len('_Proxy'):] | ||
237 | if(name.startswith('_ObjectProxy')): | ||
238 | name = name[len('_ObjectProxy'):] | ||
239 | return name | ||
240 | |||
241 | |||
242 | class Proxy(object): | ||
243 | """\ | ||
244 | @class Proxy | ||
245 | @brief This class wraps a remote python process, presumably available | ||
246 | in an instance of an Server. | ||
247 | |||
248 | This is the class you will typically use as a client to a child | ||
249 | process. Simply instantiate one around a file-like interface and start | ||
250 | calling methods on the thing that is exported. The dir() builtin is | ||
251 | not supported, so you have to know what has been exported. | ||
252 | """ | ||
253 | def __init__(self, input, output, dead_list = None): | ||
254 | """\ | ||
255 | @param input a file-like object which supports read(). | ||
256 | @param output a file-like object which supports write() and flush(). | ||
257 | @param id an identifier for the remote object. humans do not provide this. | ||
258 | """ | ||
259 | # default dead_list inside the function because all objects in method | ||
260 | # argument lists are init-ed only once globally | ||
261 | if dead_list is None: | ||
262 | dead_list = set() | ||
263 | #_prnt("Proxy::__init__") | ||
264 | self.__local_dict = dict( | ||
265 | _in = input, | ||
266 | _out = output, | ||
267 | _dead_list = dead_list, | ||
268 | _id = None) | ||
269 | |||
270 | def __getattribute__(self, attribute): | ||
271 | #_prnt("Proxy::__getattr__: %s" % attribute) | ||
272 | if _is_local(attribute): | ||
273 | # call base class getattribute so we actually get the local variable | ||
274 | attribute = _unmunge_attr_name(attribute) | ||
275 | return super(Proxy, self).__getattribute__(attribute) | ||
276 | else: | ||
277 | my_in = self.__local_dict['_in'] | ||
278 | my_out = self.__local_dict['_out'] | ||
279 | my_id = self.__local_dict['_id'] | ||
280 | _dead_list = self.__local_dict['_dead_list'] | ||
281 | for dead_object in _dead_list: | ||
282 | request = Request('del', {'id':dead_object}) | ||
283 | _write_request(request, my_out) | ||
284 | response = _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
285 | _dead_list.clear() | ||
286 | |||
287 | # Pass all public attributes across to find out if it is | ||
288 | # callable or a simple attribute. | ||
289 | request = Request('getattr', {'id':my_id, 'attribute':attribute}) | ||
290 | _write_request(request, my_out) | ||
291 | return _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
292 | |||
293 | def __setattr__(self, attribute, value): | ||
294 | #_prnt("Proxy::__setattr__: %s" % attribute) | ||
295 | if _is_local(attribute): | ||
296 | # It must be local to this actual object, so we have to apply | ||
297 | # it to the dict in a roundabout way | ||
298 | attribute = _unmunge_attr_name(attribute) | ||
299 | super(Proxy, self).__getattribute__('__dict__')[attribute]=value | ||
300 | else: | ||
301 | my_in = self.__local_dict['_in'] | ||
302 | my_out = self.__local_dict['_out'] | ||
303 | my_id = self.__local_dict['_id'] | ||
304 | _dead_list = self.__local_dict['_dead_list'] | ||
305 | # Pass the set attribute across | ||
306 | request = Request('setattr', {'id':my_id, 'attribute':attribute, 'value':value}) | ||
307 | _write_request(request, my_out) | ||
308 | return _read_response(my_id, attribute, my_in, my_out, _dead_list) | ||
309 | |||
310 | class ObjectProxy(Proxy): | ||
311 | """\ | ||
312 | @class ObjectProxy | ||
313 | @brief This class wraps a remote object in the Server | ||
314 | |||
315 | This class will be created during normal operation, and users should | ||
316 | not need to deal with this class directly.""" | ||
317 | |||
318 | def __init__(self, input, output, id, dead_list): | ||
319 | """\ | ||
320 | @param input a file-like object which supports read(). | ||
321 | @param output a file-like object which supports write() and flush(). | ||
322 | @param id an identifier for the remote object. humans do not provide this. | ||
323 | """ | ||
324 | Proxy.__init__(self, input, output, dead_list) | ||
325 | self.__local_dict['_id'] = id | ||
326 | #_prnt("ObjectProxy::__init__ %s" % self._id) | ||
327 | |||
328 | def __del__(self): | ||
329 | my_id = self.__local_dict['_id'] | ||
330 | #_prnt"ObjectProxy::__del__ %s" % my_id | ||
331 | self.__local_dict['_dead_list'].add(my_id) | ||
332 | |||
333 | def __getitem__(self, key): | ||
334 | my_in = self.__local_dict['_in'] | ||
335 | my_out = self.__local_dict['_out'] | ||
336 | my_id = self.__local_dict['_id'] | ||
337 | _dead_list = self.__local_dict['_dead_list'] | ||
338 | request = Request('getitem', {'id':my_id, 'key':key}) | ||
339 | _write_request(request, my_out) | ||
340 | return _read_response(my_id, key, my_in, my_out, _dead_list) | ||
341 | |||
342 | def __setitem__(self, key, value): | ||
343 | my_in = self.__local_dict['_in'] | ||
344 | my_out = self.__local_dict['_out'] | ||
345 | my_id = self.__local_dict['_id'] | ||
346 | _dead_list = self.__local_dict['_dead_list'] | ||
347 | request = Request('setitem', {'id':my_id, 'key':key, 'value':value}) | ||
348 | _write_request(request, my_out) | ||
349 | return _read_response(my_id, key, my_in, my_out, _dead_list) | ||
350 | |||
351 | def __eq__(self, rhs): | ||
352 | my_in = self.__local_dict['_in'] | ||
353 | my_out = self.__local_dict['_out'] | ||
354 | my_id = self.__local_dict['_id'] | ||
355 | _dead_list = self.__local_dict['_dead_list'] | ||
356 | request = Request('eq', {'id':my_id, 'rhs':rhs.__local_dict['_id']}) | ||
357 | _write_request(request, my_out) | ||
358 | return _read_response(my_id, None, my_in, my_out, _dead_list) | ||
359 | |||
360 | def __repr__(self): | ||
361 | # apparently repr(obj) skips the whole getattribute thing and just calls __repr__ | ||
362 | # directly. Therefore we just pass it through the normal call pipeline, and | ||
363 | # tack on a little header so that you can tell it's an object proxy. | ||
364 | val = self.__repr__() | ||
365 | return "saran:%s" % val | ||
366 | |||
367 | def __str__(self): | ||
368 | # see description for __repr__, because str(obj) works the same. We don't | ||
369 | # tack anything on to the return value here because str values are used as data. | ||
370 | return self.__str__() | ||
371 | |||
372 | def proxied_type(self): | ||
373 | if type(self) is not ObjectProxy: | ||
374 | return type(self) | ||
375 | |||
376 | my_in = self.__local_dict['_in'] | ||
377 | my_out = self.__local_dict['_out'] | ||
378 | my_id = self.__local_dict['_id'] | ||
379 | request = Request('type', {'id':my_id}) | ||
380 | _write_request(request, my_out) | ||
381 | # dead list can be none because we know the result will always be | ||
382 | # a value and not an ObjectProxy itself | ||
383 | return _read_response(my_id, None, my_in, my_out, None) | ||
384 | |||
385 | class CallableProxy(object): | ||
386 | """\ | ||
387 | @class CallableProxy | ||
388 | @brief This class wraps a remote function in the Server | ||
389 | |||
390 | This class will be created by an Proxy during normal operation, | ||
391 | and users should not need to deal with this class directly.""" | ||
392 | |||
393 | def __init__(self, object_id, name, input, output, dead_list): | ||
394 | #_prnt("CallableProxy::__init__: %s, %s" % (object_id, name)) | ||
395 | self._object_id = object_id | ||
396 | self._name = name | ||
397 | self._in = input | ||
398 | self._out = output | ||
399 | self._dead_list = dead_list | ||
400 | |||
401 | def __call__(self, *args, **kwargs): | ||
402 | #_prnt("CallableProxy::__call__: %s, %s" % (args, kwargs)) | ||
403 | |||
404 | # Pass the call across. We never build a callable without | ||
405 | # having already checked if the method starts with '_' so we | ||
406 | # can safely pass this one to the remote object. | ||
407 | #_prnt("calling %s %s" % (self._object_id, self._name) | ||
408 | request = Request('call', {'id':self._object_id, 'name':self._name, 'args':args, 'kwargs':kwargs}) | ||
409 | _write_request(request, self._out) | ||
410 | return _read_response(self._object_id, self._name, self._in, self._out, self._dead_list) | ||
411 | |||
412 | class Server(object): | ||
413 | def __init__(self, input, output, export): | ||
414 | """\ | ||
415 | @param input a file-like object which supports read(). | ||
416 | @param output a file-like object which supports write() and flush(). | ||
417 | @param export an object, function, or map which is exported to clients | ||
418 | when the id is None.""" | ||
419 | #_log("Server::__init__") | ||
420 | self._in = input | ||
421 | self._out = output | ||
422 | self._export = export | ||
423 | self._next_id = 1 | ||
424 | self._objects = {} | ||
425 | |||
426 | def handle_status(self, object, req): | ||
427 | return { | ||
428 | 'object_count':len(self._objects), | ||
429 | 'next_id':self._next_id, | ||
430 | 'pid':os.getpid()} | ||
431 | |||
432 | def handle_getattr(self, object, req): | ||
433 | try: | ||
434 | return getattr(object, req['attribute']) | ||
435 | except AttributeError, e: | ||
436 | if hasattr(object, "__getitem__"): | ||
437 | return object[req['attribute']] | ||
438 | else: | ||
439 | raise e | ||
440 | #_log('getattr: %s' % str(response)) | ||
441 | |||
442 | def handle_setattr(self, object, req): | ||
443 | try: | ||
444 | return setattr(object, req['attribute'], req['value']) | ||
445 | except AttributeError, e: | ||
446 | if hasattr(object, "__setitem__"): | ||
447 | return object.__setitem__(req['attribute'], req['value']) | ||
448 | else: | ||
449 | raise e | ||
450 | |||
451 | def handle_getitem(self, object, req): | ||
452 | return object[req['key']] | ||
453 | |||
454 | def handle_setitem(self, object, req): | ||
455 | object[req['key']] = req['value'] | ||
456 | return None # *TODO figure out what the actual return value of __setitem__ should be | ||
457 | |||
458 | def handle_eq(self, object, req): | ||
459 | #_log("__eq__ %s %s" % (object, req)) | ||
460 | rhs = None | ||
461 | try: | ||
462 | rhs = self._objects[req['rhs']] | ||
463 | except KeyError, e: | ||
464 | return False | ||
465 | return (object == rhs) | ||
466 | |||
467 | def handle_call(self, object, req): | ||
468 | #_log("calling %s " % (req['name'])) | ||
469 | try: | ||
470 | fn = getattr(object, req['name']) | ||
471 | except AttributeError, e: | ||
472 | if hasattr(object, "__setitem__"): | ||
473 | fn = object[req['name']] | ||
474 | else: | ||
475 | raise e | ||
476 | |||
477 | return fn(*req['args'],**req['kwargs']) | ||
478 | |||
479 | def handle_del(self, object, req): | ||
480 | id = req['id'] | ||
481 | _log("del %s from %s" % (id, self._objects)) | ||
482 | |||
483 | # *TODO what does __del__ actually return? | ||
484 | del self._objects[id] | ||
485 | return None | ||
486 | |||
487 | def handle_type(self, object, req): | ||
488 | return type(object) | ||
489 | |||
490 | def loop(self): | ||
491 | """@brief Loop forever and respond to all requests.""" | ||
492 | _log("Server::loop") | ||
493 | while True: | ||
494 | try: | ||
495 | try: | ||
496 | str = _read_lp_hunk(self._in) | ||
497 | except EOFError: | ||
498 | sys.exit(0) # normal exit | ||
499 | request = cPickle.loads(str) | ||
500 | _log("request: %s (%s)" % (request, self._objects)) | ||
501 | req = request | ||
502 | id = None | ||
503 | object = None | ||
504 | try: | ||
505 | id = req['id'] | ||
506 | if id: | ||
507 | id = int(id) | ||
508 | object = self._objects[id] | ||
509 | #_log("id, object: %d %s" % (id, object)) | ||
510 | except Exception, e: | ||
511 | #_log("Exception %s" % str(e)) | ||
512 | pass | ||
513 | if object is None or id is None: | ||
514 | id = None | ||
515 | object = self._export | ||
516 | #_log("found object %s" % str(object)) | ||
517 | |||
518 | # Handle the request via a method with a special name on the server | ||
519 | handler_name = 'handle_%s' % request.action() | ||
520 | |||
521 | try: | ||
522 | handler = getattr(self, handler_name) | ||
523 | except AttributeError: | ||
524 | raise BadRequest, request.action() | ||
525 | |||
526 | response = handler(object, request) | ||
527 | |||
528 | # figure out what to do with the response, and respond | ||
529 | # apprpriately. | ||
530 | if request.action() in ['status', 'type']: | ||
531 | # have to handle these specially since we want to | ||
532 | # pickle up the actual value and not return a proxy | ||
533 | self.respond(['value', response]) | ||
534 | elif callable(response): | ||
535 | #_log("callable %s" % response) | ||
536 | self.respond(['callable']) | ||
537 | elif self.is_value(response): | ||
538 | self.respond(['value', response]) | ||
539 | else: | ||
540 | self._objects[self._next_id] = response | ||
541 | #_log("objects: %s" % str(self._objects)) | ||
542 | self.respond(['object', self._next_id]) | ||
543 | self._next_id += 1 | ||
544 | except SystemExit, e: | ||
545 | raise e | ||
546 | except Exception, e: | ||
547 | self.write_exception(e) | ||
548 | except: | ||
549 | self.write_exception(sys.exc_info()[0]) | ||
550 | |||
551 | def is_value(self, value): | ||
552 | """\ | ||
553 | @brief Test if value should be serialized as a simple dataset. | ||
554 | @param value The value to test. | ||
555 | @return Returns true if value is a simple serializeable set of data. | ||
556 | """ | ||
557 | return type(value) in (str,int,float,long,bool,type(None)) | ||
558 | |||
559 | def respond(self, body): | ||
560 | _log("responding with: %s" % body) | ||
561 | #_log("objects: %s" % self._objects) | ||
562 | s = cPickle.dumps(body) | ||
563 | _log(`s`) | ||
564 | str = _write_lp_hunk(self._out, s) | ||
565 | |||
566 | def write_exception(self, e): | ||
567 | """@brief Helper method to respond with an exception.""" | ||
568 | #_log("exception: %s" % sys.exc_info()[0]) | ||
569 | # TODO: serialize traceback using generalization of code from mulib.htmlexception | ||
570 | self.respond(['exception', e]) | ||
571 | global _g_debug_mode | ||
572 | if _g_debug_mode: | ||
573 | _log("traceback: %s" % traceback.format_tb(sys.exc_info()[2])) | ||
574 | |||
575 | |||
576 | # test function used for testing that final except clause | ||
577 | def raise_a_weird_error(): | ||
578 | raise "oh noes you can raise a string" | ||
579 | |||
580 | # test function used for testing return of unpicklable exceptions | ||
581 | def raise_an_unpicklable_error(): | ||
582 | class Unpicklable(Exception): | ||
583 | pass | ||
584 | raise Unpicklable() | ||
585 | |||
586 | # test function used for testing return of picklable exceptions | ||
587 | def raise_standard_error(): | ||
588 | raise FloatingPointError() | ||
589 | |||
590 | # test function to make sure print doesn't break the wrapper | ||
591 | def print_string(str): | ||
592 | print str | ||
593 | |||
594 | # test function to make sure printing on stdout doesn't break the | ||
595 | # wrapper | ||
596 | def err_string(str): | ||
597 | print >>sys.stderr, str | ||
598 | |||
599 | def main(): | ||
600 | import optparse | ||
601 | parser = optparse.OptionParser( | ||
602 | usage="usage: %prog [options]", | ||
603 | description="Simple saranwrap.Server wrapper") | ||
604 | parser.add_option( | ||
605 | '-c', '--child', default=False, action='store_true', | ||
606 | help='Wrap an object serialed via setattr.') | ||
607 | parser.add_option( | ||
608 | '-m', '--module', type='string', dest='module', default=None, | ||
609 | help='a module to load and export.') | ||
610 | parser.add_option( | ||
611 | '-l', '--logfile', type='string', dest='logfile', default=None, | ||
612 | help='file to log to.') | ||
613 | options, args = parser.parse_args() | ||
614 | global _g_logfile | ||
615 | if options.logfile: | ||
616 | _g_logfile = open(options.logfile, 'a') | ||
617 | if options.module: | ||
618 | export = api.named(options.module) | ||
619 | server = Server(sys.stdin, sys.stdout, export) | ||
620 | elif options.child: | ||
621 | server = Server(sys.stdin, sys.stdout, {}) | ||
622 | |||
623 | # *HACK: some modules may emit on stderr, which breaks everything. | ||
624 | class NullSTDOut(object): | ||
625 | def write(a, b): | ||
626 | pass | ||
627 | sys.stderr = NullSTDOut() | ||
628 | sys.stdout = NullSTDOut() | ||
629 | |||
630 | # Loop until EOF | ||
631 | server.loop() | ||
632 | if _g_logfile: | ||
633 | _g_logfile.close() | ||
634 | |||
635 | |||
636 | if __name__ == "__main__": | ||
637 | main() | ||
diff --git a/linden/indra/lib/python/indra/ipc/servicebuilder.py b/linden/indra/lib/python/indra/ipc/servicebuilder.py new file mode 100644 index 0000000..ebd2583 --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/servicebuilder.py | |||
@@ -0,0 +1,93 @@ | |||
1 | """\ | ||
2 | @file servicebuilder.py | ||
3 | @author Phoenix | ||
4 | @brief Class which will generate service urls. | ||
5 | |||
6 | $LicenseInfo:firstyear=2007&license=mit$ | ||
7 | |||
8 | Copyright (c) 2007, Linden Research, Inc. | ||
9 | |||
10 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
11 | of this software and associated documentation files (the "Software"), to deal | ||
12 | in the Software without restriction, including without limitation the rights | ||
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
14 | copies of the Software, and to permit persons to whom the Software is | ||
15 | furnished to do so, subject to the following conditions: | ||
16 | |||
17 | The above copyright notice and this permission notice shall be included in | ||
18 | all copies or substantial portions of the Software. | ||
19 | |||
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
26 | THE SOFTWARE. | ||
27 | $/LicenseInfo$ | ||
28 | """ | ||
29 | |||
30 | from indra.base import config | ||
31 | from indra.ipc import llsdhttp | ||
32 | from indra.ipc import russ | ||
33 | |||
34 | # *NOTE: agent presence relies on this variable existing and being current, it is a huge hack | ||
35 | services_config = {} | ||
36 | try: | ||
37 | services_config = llsdhttp.get(config.get('services-config')) | ||
38 | except: | ||
39 | pass | ||
40 | |||
41 | _g_builder = None | ||
42 | def build(name, context={}, **kwargs): | ||
43 | """ Convenience method for using a global, singleton, service builder. Pass arguments either via a dict or via python keyword arguments, or both! | ||
44 | |||
45 | Example use: | ||
46 | > context = {'channel':'Second Life Release', 'version':'1.18.2.0'} | ||
47 | > servicebuilder.build('version-manager-version', context) | ||
48 | 'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.2.0' | ||
49 | > servicebuilder.build('version-manager-version', channel='Second Life Release', version='1.18.2.0') | ||
50 | 'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.2.0' | ||
51 | > servicebuilder.build('version-manager-version', context, version='1.18.1.2') | ||
52 | 'http://int.util.vaak.lindenlab.com/channel/Second%20Life%20Release/1.18.1.2' | ||
53 | """ | ||
54 | context = context.copy() # shouldn't modify the caller's dictionary | ||
55 | context.update(kwargs) | ||
56 | global _g_builder | ||
57 | if _g_builder is None: | ||
58 | _g_builder = ServiceBuilder() | ||
59 | return _g_builder.buildServiceURL(name, context) | ||
60 | |||
61 | class ServiceBuilder(object): | ||
62 | def __init__(self, services_definition = services_config): | ||
63 | """\ | ||
64 | @brief | ||
65 | @brief Create a ServiceBuilder. | ||
66 | @param services_definition Complete services definition, services.xml. | ||
67 | """ | ||
68 | # no need to keep a copy of the services section of the | ||
69 | # complete services definition, but it doesn't hurt much. | ||
70 | self.services = services_definition['services'] | ||
71 | self.builders = {} | ||
72 | for service in self.services: | ||
73 | service_builder = service.get('service-builder') | ||
74 | if not service_builder: | ||
75 | continue | ||
76 | if isinstance(service_builder, dict): | ||
77 | # We will be constructing several builders | ||
78 | for name, builder in service_builder.items(): | ||
79 | full_builder_name = service['name'] + '-' + name | ||
80 | self.builders[full_builder_name] = builder | ||
81 | else: | ||
82 | self.builders[service['name']] = service_builder | ||
83 | |||
84 | def buildServiceURL(self, name, context): | ||
85 | """\ | ||
86 | @brief given the environment on construction, return a service URL. | ||
87 | @param name The name of the service. | ||
88 | @param context A dict of name value lookups for the service. | ||
89 | @returns Returns the | ||
90 | """ | ||
91 | base_url = config.get('services-base-url') | ||
92 | svc_path = russ.format(self.builders[name], context) | ||
93 | return base_url + svc_path | ||
diff --git a/linden/indra/lib/python/indra/ipc/tokenstream.py b/linden/indra/lib/python/indra/ipc/tokenstream.py index 83b087a..37896d3 100644 --- a/linden/indra/lib/python/indra/ipc/tokenstream.py +++ b/linden/indra/lib/python/indra/ipc/tokenstream.py | |||
@@ -2,28 +2,28 @@ | |||
2 | @file tokenstream.py | 2 | @file tokenstream.py |
3 | @brief Message template parsing utility class | 3 | @brief Message template parsing utility class |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
5 | Copyright (c) 2007, Linden Research, Inc. | 7 | Copyright (c) 2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | of this software and associated documentation files (the "Software"), to deal |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | in the Software without restriction, including without limitation the rights |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | copies of the Software, and to permit persons to whom the Software is |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | furnished to do so, subject to the following conditions: |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | The above copyright notice and this permission notice shall be included in |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | all copies or substantial portions of the Software. |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | # online at http://secondlife.com/developers/opensource/flossexception | 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | # | 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | # By copying, modifying or distributing this software, you acknowledge | 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | # that you have read and understood your obligations described above, | 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | # and agree to abide by those obligations. | 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | # | 25 | THE SOFTWARE. |
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 26 | $/LicenseInfo$ |
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 27 | """ |
28 | 28 | ||
29 | import re | 29 | import re |
diff --git a/linden/indra/lib/python/indra/ipc/webdav.py b/linden/indra/lib/python/indra/ipc/webdav.py new file mode 100644 index 0000000..66e55ca --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/webdav.py | |||
@@ -0,0 +1,597 @@ | |||
1 | """ | ||
2 | @file webdav.py | ||
3 | @brief Classes to make manipulation of a webdav store easier. | ||
4 | |||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
7 | Copyright (c) 2007, 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 | import sys, os, httplib, urlparse | ||
30 | import socket, time | ||
31 | import xml.dom.minidom | ||
32 | import syslog | ||
33 | # import signal | ||
34 | |||
35 | __revision__ = '0' | ||
36 | |||
37 | dav_debug = False | ||
38 | |||
39 | |||
40 | # def urlsafe_b64decode (enc): | ||
41 | # return base64.decodestring (enc.replace ('_', '/').replace ('-', '+')) | ||
42 | |||
43 | # def urlsafe_b64encode (str): | ||
44 | # return base64.encodestring (str).replace ('+', '-').replace ('/', '_') | ||
45 | |||
46 | |||
47 | class DAVError (Exception): | ||
48 | """ Base class for exceptions in this module. """ | ||
49 | def __init__ (self, status=0, message='', body='', details=''): | ||
50 | self.status = status | ||
51 | self.message = message | ||
52 | self.body = body | ||
53 | self.details = details | ||
54 | Exception.__init__ (self, '%d:%s:%s%s' % (self.status, self.message, | ||
55 | self.body, self.details)) | ||
56 | |||
57 | def print_to_stderr (self): | ||
58 | """ print_to_stderr docstring """ | ||
59 | print >> sys.stderr, str (self.status) + ' ' + self.message | ||
60 | print >> sys.stderr, str (self.details) | ||
61 | |||
62 | |||
63 | class Timeout (Exception): | ||
64 | """ Timeout docstring """ | ||
65 | def __init__ (self, arg=''): | ||
66 | Exception.__init__ (self, arg) | ||
67 | |||
68 | |||
69 | def alarm_handler (signum, frame): | ||
70 | """ alarm_handler docstring """ | ||
71 | raise Timeout ('caught alarm') | ||
72 | |||
73 | |||
74 | class WebDAV: | ||
75 | """ WebDAV docstring """ | ||
76 | def __init__ (self, url, proxy=None, retries_before_fail=6): | ||
77 | self.init_url = url | ||
78 | self.init_proxy = proxy | ||
79 | self.retries_before_fail = retries_before_fail | ||
80 | url_parsed = urlparse.urlsplit (url) | ||
81 | |||
82 | self.top_path = url_parsed[ 2 ] | ||
83 | # make sure top_path has a trailing / | ||
84 | if self.top_path == None or self.top_path == '': | ||
85 | self.top_path = '/' | ||
86 | elif len (self.top_path) > 1 and self.top_path[-1:] != '/': | ||
87 | self.top_path += '/' | ||
88 | |||
89 | if dav_debug: | ||
90 | syslog.syslog ('new WebDAV %s : %s' % (str (url), str (proxy))) | ||
91 | |||
92 | if proxy: | ||
93 | proxy_parsed = urlparse.urlsplit (proxy) | ||
94 | self.host_header = url_parsed[ 1 ] | ||
95 | host_and_port = proxy_parsed[ 1 ].split (':') | ||
96 | self.host = host_and_port[ 0 ] | ||
97 | if len (host_and_port) > 1: | ||
98 | self.port = int(host_and_port[ 1 ]) | ||
99 | else: | ||
100 | self.port = 80 | ||
101 | else: # no proxy | ||
102 | host_and_port = url_parsed[ 1 ].split (':') | ||
103 | self.host_header = None | ||
104 | self.host = host_and_port[ 0 ] | ||
105 | if len (host_and_port) > 1: | ||
106 | self.port = int(host_and_port[ 1 ]) | ||
107 | else: | ||
108 | self.port = 80 | ||
109 | |||
110 | self.connection = False | ||
111 | self.connect () | ||
112 | |||
113 | |||
114 | def log (self, msg, depth=0): | ||
115 | """ log docstring """ | ||
116 | if dav_debug and depth == 0: | ||
117 | host = str (self.init_url) | ||
118 | if host == 'http://int.tuco.lindenlab.com:80/asset/': | ||
119 | host = 'tuco' | ||
120 | if host == 'http://harriet.lindenlab.com/asset-keep/': | ||
121 | host = 'harriet/asset-keep' | ||
122 | if host == 'http://harriet.lindenlab.com/asset-flag/': | ||
123 | host = 'harriet/asset-flag' | ||
124 | if host == 'http://harriet.lindenlab.com/asset/': | ||
125 | host = 'harriet/asset' | ||
126 | if host == 'http://ozzy.lindenlab.com/asset/': | ||
127 | host = 'ozzy/asset' | ||
128 | if host == 'http://station11.lindenlab.com:12041/:': | ||
129 | host = 'station11:12041' | ||
130 | proxy = str (self.init_proxy) | ||
131 | if proxy == 'None': | ||
132 | proxy = '' | ||
133 | if proxy == 'http://int.tuco.lindenlab.com:3128/': | ||
134 | proxy = 'tuco' | ||
135 | syslog.syslog ('WebDAV (%s:%s) %s' % (host, proxy, str (msg))) | ||
136 | |||
137 | |||
138 | def connect (self): | ||
139 | """ connect docstring """ | ||
140 | self.log ('connect') | ||
141 | self.connection = httplib.HTTPConnection (self.host, self.port) | ||
142 | |||
143 | def __err (self, response, details): | ||
144 | """ __err docstring """ | ||
145 | raise DAVError (response.status, response.reason, response.read (), | ||
146 | str (self.init_url) + ':' + \ | ||
147 | str (self.init_proxy) + ':' + str (details)) | ||
148 | |||
149 | def request (self, method, path, body=None, headers=None, | ||
150 | read_all=True, body_hook = None, recurse=0, allow_cache=True): | ||
151 | """ request docstring """ | ||
152 | # self.log ('request %s %s' % (method, path)) | ||
153 | if headers == None: | ||
154 | headers = {} | ||
155 | if not allow_cache: | ||
156 | headers['Pragma'] = 'no-cache' | ||
157 | headers['cache-control'] = 'no-cache' | ||
158 | try: | ||
159 | if method.lower () != 'purge': | ||
160 | if path.startswith ('/'): | ||
161 | path = path[1:] | ||
162 | if self.host_header: # use proxy | ||
163 | headers[ 'host' ] = self.host_header | ||
164 | fullpath = 'http://%s%s%s' % (self.host_header, | ||
165 | self.top_path, path) | ||
166 | else: # no proxy | ||
167 | fullpath = self.top_path + path | ||
168 | else: | ||
169 | fullpath = path | ||
170 | |||
171 | self.connection.request (method, fullpath, body, headers) | ||
172 | if body_hook: | ||
173 | body_hook () | ||
174 | |||
175 | # signal.signal (signal.SIGALRM, alarm_handler) | ||
176 | # try: | ||
177 | # signal.alarm (120) | ||
178 | # signal.alarm (0) | ||
179 | # except Timeout, e: | ||
180 | # if recurse < 6: | ||
181 | # return self.retry_request (method, path, body, headers, | ||
182 | # read_all, body_hook, recurse) | ||
183 | # else: | ||
184 | # raise DAVError (0, 'timeout', self.host, | ||
185 | # (method, path, body, headers, recurse)) | ||
186 | |||
187 | response = self.connection.getresponse () | ||
188 | |||
189 | if read_all: | ||
190 | while len (response.read (1024)) > 0: | ||
191 | pass | ||
192 | if (response.status == 500 or \ | ||
193 | response.status == 503 or \ | ||
194 | response.status == 403) and \ | ||
195 | recurse < self.retries_before_fail: | ||
196 | return self.retry_request (method, path, body, headers, | ||
197 | read_all, body_hook, recurse) | ||
198 | return response | ||
199 | except (httplib.ResponseNotReady, | ||
200 | httplib.BadStatusLine, | ||
201 | socket.error): | ||
202 | # if the server hangs up on us (keepalive off, broken pipe), | ||
203 | # we need to reconnect and try again. | ||
204 | if recurse < self.retries_before_fail: | ||
205 | return self.retry_request (method, path, body, headers, | ||
206 | read_all, body_hook, recurse) | ||
207 | raise DAVError (0, 'reconnect failed', self.host, | ||
208 | (method, path, body, headers, recurse)) | ||
209 | |||
210 | |||
211 | def retry_request (self, method, path, body, headers, | ||
212 | read_all, body_hook, recurse): | ||
213 | """ retry_request docstring """ | ||
214 | time.sleep (10.0 * recurse) | ||
215 | self.connect () | ||
216 | return self.request (method, path, body, headers, | ||
217 | read_all, body_hook, recurse+1) | ||
218 | |||
219 | |||
220 | |||
221 | def propfind (self, path, body=None, depth=1): | ||
222 | """ propfind docstring """ | ||
223 | # self.log ('propfind %s' % path) | ||
224 | headers = {'Content-Type':'text/xml; charset="utf-8"', | ||
225 | 'Depth':str(depth)} | ||
226 | response = self.request ('PROPFIND', path, body, headers, False) | ||
227 | if response.status == 207: | ||
228 | return response # Multi-Status | ||
229 | self.__err (response, ('PROPFIND', path, body, headers, 0)) | ||
230 | |||
231 | |||
232 | def purge (self, path): | ||
233 | """ issue a squid purge command """ | ||
234 | headers = {'Accept':'*/*'} | ||
235 | response = self.request ('PURGE', path, None, headers) | ||
236 | if response.status == 200 or response.status == 404: | ||
237 | # 200 if it was purge, 404 if it wasn't there. | ||
238 | return response | ||
239 | self.__err (response, ('PURGE', path, None, headers)) | ||
240 | |||
241 | |||
242 | def get_file_size (self, path): | ||
243 | """ | ||
244 | Use propfind to ask a webdav server what the size of | ||
245 | a file is. If used on a directory (collection) return 0 | ||
246 | """ | ||
247 | self.log ('get_file_size %s' % path) | ||
248 | # "getcontentlength" property | ||
249 | # 8.1.1 Example - Retrieving Named Properties | ||
250 | # http://docs.python.org/lib/module-xml.dom.html | ||
251 | nsurl = 'http://apache.org/dav/props/' | ||
252 | doc = xml.dom.minidom.Document () | ||
253 | propfind_element = doc.createElementNS (nsurl, "D:propfind") | ||
254 | propfind_element.setAttributeNS (nsurl, 'xmlns:D', 'DAV:') | ||
255 | doc.appendChild (propfind_element) | ||
256 | prop_element = doc.createElementNS (nsurl, "D:prop") | ||
257 | propfind_element.appendChild (prop_element) | ||
258 | con_len_element = doc.createElementNS (nsurl, "D:getcontentlength") | ||
259 | prop_element.appendChild (con_len_element) | ||
260 | |||
261 | response = self.propfind (path, doc.toxml ()) | ||
262 | doc.unlink () | ||
263 | |||
264 | resp_doc = xml.dom.minidom.parseString (response.read ()) | ||
265 | cln = resp_doc.getElementsByTagNameNS ('DAV:','getcontentlength')[ 0 ] | ||
266 | try: | ||
267 | content_length = int (cln.childNodes[ 0 ].nodeValue) | ||
268 | except IndexError: | ||
269 | return 0 | ||
270 | resp_doc.unlink () | ||
271 | return content_length | ||
272 | |||
273 | |||
274 | def file_exists (self, path): | ||
275 | """ | ||
276 | do an http head on the given file. return True if it succeeds | ||
277 | """ | ||
278 | self.log ('file_exists %s' % path) | ||
279 | expect_gzip = path.endswith ('.gz') | ||
280 | response = self.request ('HEAD', path) | ||
281 | got_gzip = response.getheader ('Content-Encoding', '').strip () | ||
282 | if got_gzip.lower () == 'x-gzip' and expect_gzip == False: | ||
283 | # the asset server fakes us out if we ask for the non-gzipped | ||
284 | # version of an asset, but the server has the gzipped version. | ||
285 | return False | ||
286 | return response.status == 200 | ||
287 | |||
288 | |||
289 | def mkdir (self, path): | ||
290 | """ mkdir docstring """ | ||
291 | self.log ('mkdir %s' % path) | ||
292 | headers = {} | ||
293 | response = self.request ('MKCOL', path, None, headers) | ||
294 | if response.status == 201: | ||
295 | return # success | ||
296 | if response.status == 405: | ||
297 | return # directory already existed? | ||
298 | self.__err (response, ('MKCOL', path, None, headers, 0)) | ||
299 | |||
300 | |||
301 | def delete (self, path): | ||
302 | """ delete docstring """ | ||
303 | self.log ('delete %s' % path) | ||
304 | headers = {'Depth':'infinity'} # collections require infinity | ||
305 | response = self.request ('DELETE', path, None, headers) | ||
306 | if response.status == 204: | ||
307 | return # no content | ||
308 | if response.status == 404: | ||
309 | return # hmm | ||
310 | self.__err (response, ('DELETE', path, None, headers, 0)) | ||
311 | |||
312 | |||
313 | def list_directory (self, path, dir_filter=None, allow_cache=True, | ||
314 | minimum_cache_time=False): | ||
315 | """ | ||
316 | Request an http directory listing and parse the filenames out of lines | ||
317 | like: '<LI><A HREF="X"> X</A>'. If a filter function is provided, | ||
318 | only return filenames that the filter returns True for. | ||
319 | |||
320 | This is sort of grody, but it seems faster than other ways of getting | ||
321 | this information from an isilon. | ||
322 | """ | ||
323 | self.log ('list_directory %s' % path) | ||
324 | |||
325 | def try_match (lline, before, after): | ||
326 | """ try_match docstring """ | ||
327 | try: | ||
328 | blen = len (before) | ||
329 | asset_start_index = lline.index (before) | ||
330 | asset_end_index = lline.index (after, asset_start_index + blen) | ||
331 | asset = line[ asset_start_index + blen : asset_end_index ] | ||
332 | |||
333 | if not dir_filter or dir_filter (asset): | ||
334 | return [ asset ] | ||
335 | return [] | ||
336 | except ValueError: | ||
337 | return [] | ||
338 | |||
339 | if len (path) > 0 and path[-1:] != '/': | ||
340 | path += '/' | ||
341 | |||
342 | response = self.request ('GET', path, None, {}, False, | ||
343 | allow_cache=allow_cache) | ||
344 | |||
345 | if allow_cache and minimum_cache_time: # XXX | ||
346 | print response.getheader ('Date') | ||
347 | # s = "2005-12-06T12:13:14" | ||
348 | # from datetime import datetime | ||
349 | # from time import strptime | ||
350 | # datetime(*strptime(s, "%Y-%m-%dT%H:%M:%S")[0:6]) | ||
351 | # datetime.datetime(2005, 12, 6, 12, 13, 14) | ||
352 | |||
353 | if response.status != 200: | ||
354 | self.__err (response, ('GET', path, None, {}, 0)) | ||
355 | assets = [] | ||
356 | for line in response.read ().split ('\n'): | ||
357 | lline = line.lower () | ||
358 | if lline.find ("parent directory") == -1: | ||
359 | # isilon file | ||
360 | assets += try_match (lline, '<li><a href="', '"> ') | ||
361 | # apache dir | ||
362 | assets += try_match (lline, 'alt="[dir]"> <a href="', '/">') | ||
363 | # apache file | ||
364 | assets += try_match (lline, 'alt="[ ]"> <a href="', '">') | ||
365 | return assets | ||
366 | |||
367 | |||
368 | def __tmp_filename (self, path_and_file): | ||
369 | """ __tmp_filename docstring """ | ||
370 | head, tail = os.path.split (path_and_file) | ||
371 | if head != '': | ||
372 | return head + '/.' + tail + '.' + str (os.getpid ()) | ||
373 | else: | ||
374 | return head + '.' + tail + '.' + str (os.getpid ()) | ||
375 | |||
376 | |||
377 | def __put__ (self, filesize, body_hook, remotefile): | ||
378 | """ __put__ docstring """ | ||
379 | headers = {'Content-Length' : str (filesize)} | ||
380 | remotefile_tmp = self.__tmp_filename (remotefile) | ||
381 | response = self.request ('PUT', remotefile_tmp, None, | ||
382 | headers, True, body_hook) | ||
383 | if not response.status in (201, 204): # created, no content | ||
384 | self.__err (response, ('PUT', remotefile, None, headers, 0)) | ||
385 | if filesize != self.get_file_size (remotefile_tmp): | ||
386 | try: | ||
387 | self.delete (remotefile_tmp) | ||
388 | except: | ||
389 | pass | ||
390 | raise DAVError (0, 'tmp upload error', remotefile_tmp) | ||
391 | # move the file to its final location | ||
392 | try: | ||
393 | self.rename (remotefile_tmp, remotefile) | ||
394 | except DAVError, exc: | ||
395 | if exc.status == 403: # try to clean up the tmp file | ||
396 | try: | ||
397 | self.delete (remotefile_tmp) | ||
398 | except: | ||
399 | pass | ||
400 | raise | ||
401 | if filesize != self.get_file_size (remotefile): | ||
402 | raise DAVError (0, 'file upload error', str (remotefile_tmp)) | ||
403 | |||
404 | |||
405 | def put_string (self, strng, remotefile): | ||
406 | """ put_string docstring """ | ||
407 | self.log ('put_string %d -> %s' % (len (strng), remotefile)) | ||
408 | filesize = len (strng) | ||
409 | def body_hook (): | ||
410 | """ body_hook docstring """ | ||
411 | self.connection.send (strng) | ||
412 | self.__put__ (filesize, body_hook, remotefile) | ||
413 | |||
414 | |||
415 | def put_file (self, localfile, remotefile): | ||
416 | """ | ||
417 | Send a local file to a remote webdav store. First, upload to | ||
418 | a temporary filename. Next make sure the file is the size we | ||
419 | expected. Next, move the file to its final location. Next, | ||
420 | check the file size at the final location. | ||
421 | """ | ||
422 | self.log ('put_file %s -> %s' % (localfile, remotefile)) | ||
423 | filesize = os.path.getsize (localfile) | ||
424 | def body_hook (): | ||
425 | """ body_hook docstring """ | ||
426 | handle = open (localfile) | ||
427 | while True: | ||
428 | data = handle.read (1300) | ||
429 | if len (data) == 0: | ||
430 | break | ||
431 | self.connection.send (data) | ||
432 | handle.close () | ||
433 | self.__put__ (filesize, body_hook, remotefile) | ||
434 | |||
435 | |||
436 | def create_empty_file (self, remotefile): | ||
437 | """ create an empty file """ | ||
438 | self.log ('touch_file %s' % (remotefile)) | ||
439 | headers = {'Content-Length' : '0'} | ||
440 | response = self.request ('PUT', remotefile, None, headers) | ||
441 | if not response.status in (201, 204): # created, no content | ||
442 | self.__err (response, ('PUT', remotefile, None, headers, 0)) | ||
443 | if self.get_file_size (remotefile) != 0: | ||
444 | raise DAVError (0, 'file upload error', str (remotefile)) | ||
445 | |||
446 | |||
447 | def __get_file_setup (self, remotefile, check_size=True): | ||
448 | """ __get_file_setup docstring """ | ||
449 | if check_size: | ||
450 | remotesize = self.get_file_size (remotefile) | ||
451 | response = self.request ('GET', remotefile, None, {}, False) | ||
452 | if response.status != 200: | ||
453 | self.__err (response, ('GET', remotefile, None, {}, 0)) | ||
454 | try: | ||
455 | content_length = int (response.getheader ("Content-Length")) | ||
456 | except TypeError: | ||
457 | content_length = None | ||
458 | if check_size: | ||
459 | if content_length != remotesize: | ||
460 | raise DAVError (0, 'file DL size error', remotefile) | ||
461 | return (response, content_length) | ||
462 | |||
463 | |||
464 | def __get_file_read (self, writehandle, response, content_length): | ||
465 | """ __get_file_read docstring """ | ||
466 | if content_length != None: | ||
467 | so_far_length = 0 | ||
468 | while so_far_length < content_length: | ||
469 | data = response.read (content_length - so_far_length) | ||
470 | if len (data) == 0: | ||
471 | raise DAVError (0, 'short file download') | ||
472 | so_far_length += len (data) | ||
473 | writehandle.write (data) | ||
474 | while len (response.read ()) > 0: | ||
475 | pass | ||
476 | else: | ||
477 | while True: | ||
478 | data = response.read () | ||
479 | if (len (data) < 1): | ||
480 | break | ||
481 | writehandle.write (data) | ||
482 | |||
483 | |||
484 | def get_file (self, remotefile, localfile, check_size=True): | ||
485 | """ | ||
486 | Get a remote file from a webdav server. Download to a local | ||
487 | tmp file, then move into place. Sanity check file sizes as | ||
488 | we go. | ||
489 | """ | ||
490 | self.log ('get_file %s -> %s' % (remotefile, localfile)) | ||
491 | (response, content_length) = \ | ||
492 | self.__get_file_setup (remotefile, check_size) | ||
493 | localfile_tmp = self.__tmp_filename (localfile) | ||
494 | handle = open (localfile_tmp, 'w') | ||
495 | self.__get_file_read (handle, response, content_length) | ||
496 | handle.close () | ||
497 | if check_size: | ||
498 | if content_length != os.path.getsize (localfile_tmp): | ||
499 | raise DAVError (0, 'file DL size error', | ||
500 | remotefile+','+localfile) | ||
501 | os.rename (localfile_tmp, localfile) | ||
502 | |||
503 | |||
504 | def get_file_as_string (self, remotefile, check_size=True): | ||
505 | """ | ||
506 | download a file from a webdav server and return it as a string. | ||
507 | """ | ||
508 | self.log ('get_file_as_string %s' % remotefile) | ||
509 | (response, content_length) = \ | ||
510 | self.__get_file_setup (remotefile, check_size) | ||
511 | # (tmp_handle, tmp_filename) = tempfile.mkstemp () | ||
512 | tmp_handle = os.tmpfile () | ||
513 | self.__get_file_read (tmp_handle, response, content_length) | ||
514 | tmp_handle.seek (0) | ||
515 | ret = tmp_handle.read () | ||
516 | tmp_handle.close () | ||
517 | # os.unlink (tmp_filename) | ||
518 | return ret | ||
519 | |||
520 | |||
521 | def get_post_as_string (self, remotefile, body): | ||
522 | """ | ||
523 | Do an http POST, send body, get response and return it. | ||
524 | """ | ||
525 | self.log ('get_post_as_string %s' % remotefile) | ||
526 | # headers = {'Content-Type':'application/x-www-form-urlencoded'} | ||
527 | headers = {'Content-Type':'text/xml; charset="utf-8"'} | ||
528 | # b64body = urlsafe_b64encode (asset_url) | ||
529 | response = self.request ('POST', remotefile, body, headers, False) | ||
530 | if response.status != 200: | ||
531 | self.__err (response, ('POST', remotefile, body, headers, 0)) | ||
532 | try: | ||
533 | content_length = int (response.getheader ('Content-Length')) | ||
534 | except TypeError: | ||
535 | content_length = None | ||
536 | tmp_handle = os.tmpfile () | ||
537 | self.__get_file_read (tmp_handle, response, content_length) | ||
538 | tmp_handle.seek (0) | ||
539 | ret = tmp_handle.read () | ||
540 | tmp_handle.close () | ||
541 | return ret | ||
542 | |||
543 | |||
544 | def __destination_command (self, verb, remotesrc, dstdav, remotedst): | ||
545 | """ | ||
546 | self and dstdav should point to the same http server. | ||
547 | """ | ||
548 | if len (remotedst) > 0 and remotedst[ 0 ] == '/': | ||
549 | remotedst = remotedst[1:] | ||
550 | headers = {'Destination': 'http://%s:%d%s%s' % (dstdav.host, | ||
551 | dstdav.port, | ||
552 | dstdav.top_path, | ||
553 | remotedst)} | ||
554 | response = self.request (verb, remotesrc, None, headers) | ||
555 | if response.status == 201: | ||
556 | return # created | ||
557 | if response.status == 204: | ||
558 | return # no content | ||
559 | self.__err (response, (verb, remotesrc, None, headers, 0)) | ||
560 | |||
561 | |||
562 | def rename (self, remotesrc, remotedst): | ||
563 | """ rename a file on a webdav server """ | ||
564 | self.log ('rename %s -> %s' % (remotesrc, remotedst)) | ||
565 | self.__destination_command ('MOVE', remotesrc, self, remotedst) | ||
566 | def xrename (self, remotesrc, dstdav, remotedst): | ||
567 | """ rename a file on a webdav server """ | ||
568 | self.log ('xrename %s -> %s' % (remotesrc, remotedst)) | ||
569 | self.__destination_command ('MOVE', remotesrc, dstdav, remotedst) | ||
570 | |||
571 | |||
572 | def copy (self, remotesrc, remotedst): | ||
573 | """ copy a file on a webdav server """ | ||
574 | self.log ('copy %s -> %s' % (remotesrc, remotedst)) | ||
575 | self.__destination_command ('COPY', remotesrc, self, remotedst) | ||
576 | def xcopy (self, remotesrc, dstdav, remotedst): | ||
577 | """ copy a file on a webdav server """ | ||
578 | self.log ('xcopy %s -> %s' % (remotesrc, remotedst)) | ||
579 | self.__destination_command ('COPY', remotesrc, dstdav, remotedst) | ||
580 | |||
581 | |||
582 | def put_string (data, url): | ||
583 | """ | ||
584 | upload string s to a url | ||
585 | """ | ||
586 | url_parsed = urlparse.urlsplit (url) | ||
587 | dav = WebDAV ('%s://%s/' % (url_parsed[ 0 ], url_parsed[ 1 ])) | ||
588 | dav.put_string (data, url_parsed[ 2 ]) | ||
589 | |||
590 | |||
591 | def get_string (url, check_size=True): | ||
592 | """ | ||
593 | return the contents of a url as a string | ||
594 | """ | ||
595 | url_parsed = urlparse.urlsplit (url) | ||
596 | dav = WebDAV ('%s://%s/' % (url_parsed[ 0 ], url_parsed[ 1 ])) | ||
597 | return dav.get_file_as_string (url_parsed[ 2 ], check_size) | ||
diff --git a/linden/indra/lib/python/indra/ipc/xml_rpc.py b/linden/indra/lib/python/indra/ipc/xml_rpc.py new file mode 100644 index 0000000..dc8f0aa --- /dev/null +++ b/linden/indra/lib/python/indra/ipc/xml_rpc.py | |||
@@ -0,0 +1,273 @@ | |||
1 | """\ | ||
2 | @file xml_rpc.py | ||
3 | @brief An implementation of a parser/generator for the XML-RPC xml format. | ||
4 | |||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
7 | Copyright (c) 2006-2007, 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 | |||
30 | from greenlet import greenlet | ||
31 | |||
32 | from mulib import mu | ||
33 | |||
34 | from xml.sax import handler | ||
35 | from xml.sax import parseString | ||
36 | |||
37 | |||
38 | # States | ||
39 | class Expected(object): | ||
40 | def __init__(self, tag): | ||
41 | self.tag = tag | ||
42 | |||
43 | def __getattr__(self, name): | ||
44 | return type(self)(name) | ||
45 | |||
46 | def __repr__(self): | ||
47 | return '%s(%r)' % ( | ||
48 | type(self).__name__, self.tag) | ||
49 | |||
50 | |||
51 | class START(Expected): | ||
52 | pass | ||
53 | |||
54 | |||
55 | class END(Expected): | ||
56 | pass | ||
57 | |||
58 | |||
59 | class STR(object): | ||
60 | tag = '' | ||
61 | |||
62 | |||
63 | START = START('') | ||
64 | END = END('') | ||
65 | |||
66 | |||
67 | class Malformed(Exception): | ||
68 | pass | ||
69 | |||
70 | |||
71 | class XMLParser(handler.ContentHandler): | ||
72 | def __init__(self, state_machine, next_states): | ||
73 | handler.ContentHandler.__init__(self) | ||
74 | self.state_machine = state_machine | ||
75 | if not isinstance(next_states, tuple): | ||
76 | next_states = (next_states, ) | ||
77 | self.next_states = next_states | ||
78 | self._character_buffer = '' | ||
79 | |||
80 | def assertState(self, state, name, *rest): | ||
81 | if not isinstance(self.next_states, tuple): | ||
82 | self.next_states = (self.next_states, ) | ||
83 | for next in self.next_states: | ||
84 | if type(state) == type(next): | ||
85 | if next.tag and next.tag != name: | ||
86 | raise Malformed( | ||
87 | "Expected %s, got %s %s %s" % ( | ||
88 | next, state, name, rest)) | ||
89 | break | ||
90 | else: | ||
91 | raise Malformed( | ||
92 | "Expected %s, got %s %s %s" % ( | ||
93 | self.next_states, state, name, rest)) | ||
94 | |||
95 | def startElement(self, name, attrs): | ||
96 | self.assertState(START, name.lower(), attrs) | ||
97 | self.next_states = self.state_machine.switch(START, (name.lower(), dict(attrs))) | ||
98 | |||
99 | def endElement(self, name): | ||
100 | if self._character_buffer.strip(): | ||
101 | characters = self._character_buffer.strip() | ||
102 | self._character_buffer = '' | ||
103 | self.assertState(STR, characters) | ||
104 | self.next_states = self.state_machine.switch(characters) | ||
105 | self.assertState(END, name.lower()) | ||
106 | self.next_states = self.state_machine.switch(END, name.lower()) | ||
107 | |||
108 | def error(self, exc): | ||
109 | self.bozo = 1 | ||
110 | self.exc = exc | ||
111 | |||
112 | def fatalError(self, exc): | ||
113 | self.error(exc) | ||
114 | raise exc | ||
115 | |||
116 | def characters(self, characters): | ||
117 | self._character_buffer += characters | ||
118 | |||
119 | |||
120 | def parse(what): | ||
121 | child = greenlet(xml_rpc) | ||
122 | me = greenlet.getcurrent() | ||
123 | startup_states = child.switch(me) | ||
124 | parser = XMLParser(child, startup_states) | ||
125 | try: | ||
126 | parseString(what, parser) | ||
127 | except Malformed: | ||
128 | print what | ||
129 | raise | ||
130 | return child.switch() | ||
131 | |||
132 | |||
133 | def xml_rpc(yielder): | ||
134 | yielder.switch(START.methodcall) | ||
135 | yielder.switch(START.methodname) | ||
136 | methodName = yielder.switch(STR) | ||
137 | yielder.switch(END.methodname) | ||
138 | |||
139 | yielder.switch(START.params) | ||
140 | |||
141 | root = None | ||
142 | params = [] | ||
143 | while True: | ||
144 | state, _ = yielder.switch(START.param, END.params) | ||
145 | if state == END: | ||
146 | break | ||
147 | |||
148 | yielder.switch(START.value) | ||
149 | |||
150 | params.append( | ||
151 | handle(yielder)) | ||
152 | |||
153 | yielder.switch(END.value) | ||
154 | yielder.switch(END.param) | ||
155 | |||
156 | yielder.switch(END.methodcall) | ||
157 | ## Resume parse | ||
158 | yielder.switch() | ||
159 | ## Return result to parse | ||
160 | return methodName.strip(), params | ||
161 | |||
162 | |||
163 | def handle(yielder): | ||
164 | _, (tag, attrs) = yielder.switch(START) | ||
165 | if tag in ['int', 'i4']: | ||
166 | result = int(yielder.switch(STR)) | ||
167 | elif tag == 'boolean': | ||
168 | result = bool(int(yielder.switch(STR))) | ||
169 | elif tag == 'string': | ||
170 | result = yielder.switch(STR) | ||
171 | elif tag == 'double': | ||
172 | result = float(yielder.switch(STR)) | ||
173 | elif tag == 'datetime.iso8601': | ||
174 | result = yielder.switch(STR) | ||
175 | elif tag == 'base64': | ||
176 | result = base64.b64decode(yielder.switch(STR)) | ||
177 | elif tag == 'struct': | ||
178 | result = {} | ||
179 | while True: | ||
180 | state, _ = yielder.switch(START.member, END.struct) | ||
181 | if state == END: | ||
182 | break | ||
183 | |||
184 | yielder.switch(START.name) | ||
185 | key = yielder.switch(STR) | ||
186 | yielder.switch(END.name) | ||
187 | |||
188 | yielder.switch(START.value) | ||
189 | result[key] = handle(yielder) | ||
190 | yielder.switch(END.value) | ||
191 | |||
192 | yielder.switch(END.member) | ||
193 | ## We already handled </struct> above, don't want to handle it below | ||
194 | return result | ||
195 | elif tag == 'array': | ||
196 | result = [] | ||
197 | yielder.switch(START.data) | ||
198 | while True: | ||
199 | state, _ = yielder.switch(START.value, END.data) | ||
200 | if state == END: | ||
201 | break | ||
202 | |||
203 | result.append(handle(yielder)) | ||
204 | |||
205 | yielder.switch(END.value) | ||
206 | |||
207 | yielder.switch(getattr(END, tag)) | ||
208 | |||
209 | return result | ||
210 | |||
211 | |||
212 | VALUE = mu.tag_factory('value') | ||
213 | BOOLEAN = mu.tag_factory('boolean') | ||
214 | INT = mu.tag_factory('int') | ||
215 | STRUCT = mu.tag_factory('struct') | ||
216 | MEMBER = mu.tag_factory('member') | ||
217 | NAME = mu.tag_factory('name') | ||
218 | ARRAY = mu.tag_factory('array') | ||
219 | DATA = mu.tag_factory('data') | ||
220 | STRING = mu.tag_factory('string') | ||
221 | DOUBLE = mu.tag_factory('double') | ||
222 | METHODRESPONSE = mu.tag_factory('methodResponse') | ||
223 | PARAMS = mu.tag_factory('params') | ||
224 | PARAM = mu.tag_factory('param') | ||
225 | |||
226 | mu.inline_elements['string'] = True | ||
227 | mu.inline_elements['boolean'] = True | ||
228 | mu.inline_elements['name'] = True | ||
229 | |||
230 | |||
231 | def _generate(something): | ||
232 | if isinstance(something, dict): | ||
233 | result = STRUCT() | ||
234 | for key, value in something.items(): | ||
235 | result[ | ||
236 | MEMBER[ | ||
237 | NAME[key], _generate(value)]] | ||
238 | return VALUE[result] | ||
239 | elif isinstance(something, list): | ||
240 | result = DATA() | ||
241 | for item in something: | ||
242 | result[_generate(item)] | ||
243 | return VALUE[ARRAY[[result]]] | ||
244 | elif isinstance(something, basestring): | ||
245 | return VALUE[STRING[something]] | ||
246 | elif isinstance(something, bool): | ||
247 | if something: | ||
248 | return VALUE[BOOLEAN['1']] | ||
249 | return VALUE[BOOLEAN['0']] | ||
250 | elif isinstance(something, int): | ||
251 | return VALUE[INT[something]] | ||
252 | elif isinstance(something, float): | ||
253 | return VALUE[DOUBLE[something]] | ||
254 | |||
255 | def generate(*args): | ||
256 | params = PARAMS() | ||
257 | for arg in args: | ||
258 | params[PARAM[_generate(arg)]] | ||
259 | return METHODRESPONSE[params] | ||
260 | |||
261 | |||
262 | if __name__ == '__main__': | ||
263 | print parse("""<?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall> | ||
264 | """) | ||
265 | |||
266 | |||
267 | |||
268 | |||
269 | |||
270 | |||
271 | |||
272 | |||
273 | |||
diff --git a/linden/indra/lib/python/indra/util/__init__.py b/linden/indra/lib/python/indra/util/__init__.py index 3f79d0a..3eda184 100644 --- a/linden/indra/lib/python/indra/util/__init__.py +++ b/linden/indra/lib/python/indra/util/__init__.py | |||
@@ -2,26 +2,26 @@ | |||
2 | @file __init__.py | 2 | @file __init__.py |
3 | @brief Initialization file for the indra util module. | 3 | @brief Initialization file for the indra util module. |
4 | 4 | ||
5 | $LicenseInfo:firstyear=2006&license=mit$ | ||
6 | |||
5 | Copyright (c) 2006-2007, Linden Research, Inc. | 7 | Copyright (c) 2006-2007, Linden Research, Inc. |
6 | 8 | ||
7 | # Second Life Viewer Source Code | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | # The source code in this file ("Source Code") is provided by Linden Lab | 10 | of this software and associated documentation files (the "Software"), to deal |
9 | # to you under the terms of the GNU General Public License, version 2.0 | 11 | in the Software without restriction, including without limitation the rights |
10 | # ("GPL"), unless you have obtained a separate licensing agreement | 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | copies of the Software, and to permit persons to whom the Software is |
12 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | furnished to do so, subject to the following conditions: |
13 | # online at http://secondlife.com/developers/opensource/gplv2 | 15 | |
14 | # | 16 | The above copyright notice and this permission notice shall be included in |
15 | # There are special exceptions to the terms and conditions of the GPL as | 17 | all copies or substantial portions of the Software. |
16 | # it is applied to this Source Code. View the full text of the exception | 18 | |
17 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | # online at http://secondlife.com/developers/opensource/flossexception | 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | # | 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | # By copying, modifying or distributing this software, you acknowledge | 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | # that you have read and understood your obligations described above, | 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | # and agree to abide by those obligations. | 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | # | 25 | THE SOFTWARE. |
24 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 26 | $/LicenseInfo$ |
25 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
26 | # COMPLETENESS OR PERFORMANCE. | ||
27 | """ | 27 | """ |
diff --git a/linden/indra/lib/python/indra/util/helpformatter.py b/linden/indra/lib/python/indra/util/helpformatter.py new file mode 100644 index 0000000..c4ff27f --- /dev/null +++ b/linden/indra/lib/python/indra/util/helpformatter.py | |||
@@ -0,0 +1,52 @@ | |||
1 | """\ | ||
2 | @file helpformatter.py | ||
3 | @author Phoenix | ||
4 | @brief Class for formatting optparse descriptions. | ||
5 | |||
6 | $LicenseInfo:firstyear=2007&license=mit$ | ||
7 | |||
8 | Copyright (c) 2007, Linden Research, Inc. | ||
9 | |||
10 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
11 | of this software and associated documentation files (the "Software"), to deal | ||
12 | in the Software without restriction, including without limitation the rights | ||
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
14 | copies of the Software, and to permit persons to whom the Software is | ||
15 | furnished to do so, subject to the following conditions: | ||
16 | |||
17 | The above copyright notice and this permission notice shall be included in | ||
18 | all copies or substantial portions of the Software. | ||
19 | |||
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
26 | THE SOFTWARE. | ||
27 | $/LicenseInfo$ | ||
28 | """ | ||
29 | |||
30 | import optparse | ||
31 | import textwrap | ||
32 | |||
33 | class Formatter(optparse.IndentedHelpFormatter): | ||
34 | def __init__( | ||
35 | self, | ||
36 | p_indentIncrement = 2, | ||
37 | p_maxHelpPosition = 24, | ||
38 | p_width = 79, | ||
39 | p_shortFirst = 1) : | ||
40 | optparse.HelpFormatter.__init__( | ||
41 | self, | ||
42 | p_indentIncrement, | ||
43 | p_maxHelpPosition, | ||
44 | p_width, | ||
45 | p_shortFirst) | ||
46 | def format_description(self, p_description): | ||
47 | t_descWidth = self.width - self.current_indent | ||
48 | t_indent = " " * (self.current_indent + 2) | ||
49 | return "\n".join( | ||
50 | [textwrap.fill(descr, t_descWidth, initial_indent = t_indent, | ||
51 | subsequent_indent = t_indent) | ||
52 | for descr in p_description.split("\n")] ) | ||
diff --git a/linden/indra/lib/python/indra/util/llmanifest.py b/linden/indra/lib/python/indra/util/llmanifest.py index c496e95..029b697 100644 --- a/linden/indra/lib/python/indra/util/llmanifest.py +++ b/linden/indra/lib/python/indra/util/llmanifest.py | |||
@@ -3,28 +3,28 @@ | |||
3 | @author Ryan Williams | 3 | @author Ryan Williams |
4 | @brief Library for specifying operations on a set of files. | 4 | @brief Library for specifying operations on a set of files. |
5 | 5 | ||
6 | $LicenseInfo:firstyear=2007&license=mit$ | ||
7 | |||
6 | Copyright (c) 2007, Linden Research, Inc. | 8 | Copyright (c) 2007, Linden Research, Inc. |
7 | 9 | ||
8 | # Second Life Viewer Source Code | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy |
9 | # The source code in this file ("Source Code") is provided by Linden Lab | 11 | of this software and associated documentation files (the "Software"), to deal |
10 | # to you under the terms of the GNU General Public License, version 2.0 | 12 | in the Software without restriction, including without limitation the rights |
11 | # ("GPL"), unless you have obtained a separate licensing agreement | 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
12 | # ("Other License"), formally executed by you and Linden Lab. Terms of | 14 | copies of the Software, and to permit persons to whom the Software is |
13 | # the GPL can be found in doc/GPL-license.txt in this distribution, or | 15 | furnished to do so, subject to the following conditions: |
14 | # online at http://secondlife.com/developers/opensource/gplv2 | 16 | |
15 | # | 17 | The above copyright notice and this permission notice shall be included in |
16 | # There are special exceptions to the terms and conditions of the GPL as | 18 | all copies or substantial portions of the Software. |
17 | # it is applied to this Source Code. View the full text of the exception | 19 | |
18 | # in the file doc/FLOSS-exception.txt in this software distribution, or | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19 | # online at http://secondlife.com/developers/opensource/flossexception | 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20 | # | 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
21 | # By copying, modifying or distributing this software, you acknowledge | 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
22 | # that you have read and understood your obligations described above, | 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
23 | # and agree to abide by those obligations. | 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
24 | # | 26 | THE SOFTWARE. |
25 | # ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 27 | $/LicenseInfo$ |
26 | # WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
27 | # COMPLETENESS OR PERFORMANCE. | ||
28 | """ | 28 | """ |
29 | 29 | ||
30 | import commands | 30 | import commands |
diff --git a/linden/indra/lib/python/indra/util/llversion.py b/linden/indra/lib/python/indra/util/llversion.py new file mode 100644 index 0000000..5e699d5 --- /dev/null +++ b/linden/indra/lib/python/indra/util/llversion.py | |||
@@ -0,0 +1,95 @@ | |||
1 | """@file llversion.py | ||
2 | @brief Utility for parsing llcommon/llversion${server}.h | ||
3 | for the version string and channel string | ||
4 | Utility that parses svn info for branch and revision | ||
5 | |||
6 | $LicenseInfo:firstyear=2006&license=mit$ | ||
7 | |||
8 | Copyright (c) 2006-2007, Linden Research, Inc. | ||
9 | |||
10 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
11 | of this software and associated documentation files (the "Software"), to deal | ||
12 | in the Software without restriction, including without limitation the rights | ||
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
14 | copies of the Software, and to permit persons to whom the Software is | ||
15 | furnished to do so, subject to the following conditions: | ||
16 | |||
17 | The above copyright notice and this permission notice shall be included in | ||
18 | all copies or substantial portions of the Software. | ||
19 | |||
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
26 | THE SOFTWARE. | ||
27 | $/LicenseInfo$ | ||
28 | """ | ||
29 | |||
30 | import re, sys, os, commands | ||
31 | |||
32 | # Methods for gathering version information from | ||
33 | # llversionviewer.h and llversionserver.h | ||
34 | |||
35 | def get_src_root(): | ||
36 | indra_lib_python_indra_path = os.path.dirname(__file__) | ||
37 | return os.path.abspath(os.path.realpath(indra_lib_python_indra_path + "/../../../../../")) | ||
38 | |||
39 | def get_version_file_contents(version_type): | ||
40 | filepath = get_src_root() + '/indra/llcommon/llversion%s.h' % version_type | ||
41 | file = open(filepath,"r") | ||
42 | file_str = file.read() | ||
43 | file.close() | ||
44 | return file_str | ||
45 | |||
46 | def get_version(version_type): | ||
47 | file_str = get_version_file_contents(version_type) | ||
48 | m = re.search('const S32 LL_VERSION_MAJOR = (\d+);', file_str) | ||
49 | VER_MAJOR = m.group(1) | ||
50 | m = re.search('const S32 LL_VERSION_MINOR = (\d+);', file_str) | ||
51 | VER_MINOR = m.group(1) | ||
52 | m = re.search('const S32 LL_VERSION_PATCH = (\d+);', file_str) | ||
53 | VER_PATCH = m.group(1) | ||
54 | m = re.search('const S32 LL_VERSION_BUILD = (\d+);', file_str) | ||
55 | VER_BUILD = m.group(1) | ||
56 | version = "%(VER_MAJOR)s.%(VER_MINOR)s.%(VER_PATCH)s.%(VER_BUILD)s" % locals() | ||
57 | return version | ||
58 | |||
59 | def get_channel(version_type): | ||
60 | file_str = get_version_file_contents(version_type) | ||
61 | m = re.search('const char \* const LL_CHANNEL = "(.+)";', file_str) | ||
62 | return m.group(1) | ||
63 | |||
64 | def get_viewer_version(): | ||
65 | return get_version('viewer') | ||
66 | |||
67 | def get_server_version(): | ||
68 | return get_version('server') | ||
69 | |||
70 | def get_viewer_channel(): | ||
71 | return get_channel('viewer') | ||
72 | |||
73 | def get_server_channel(): | ||
74 | return get_channel('server') | ||
75 | |||
76 | # Methods for gathering subversion information | ||
77 | def get_svn_status_matching(regular_expression): | ||
78 | # Get the subversion info from the working source tree | ||
79 | status, output = commands.getstatusoutput('svn info %s' % get_src_root()) | ||
80 | m = regular_expression.search(output) | ||
81 | if not m: | ||
82 | print "Failed to parse svn info output, resultfollows:" | ||
83 | print output | ||
84 | raise Exception, "No matching svn status in "+src_root | ||
85 | return m.group(1) | ||
86 | |||
87 | def get_svn_branch(): | ||
88 | branch_re = re.compile('URL: (\S+)') | ||
89 | return get_svn_status_matching(branch_re) | ||
90 | |||
91 | def get_svn_revision(): | ||
92 | last_rev_re = re.compile('Last Changed Rev: (\d+)') | ||
93 | return get_svn_status_matching(last_rev_re) | ||
94 | |||
95 | |||
diff --git a/linden/indra/lib/python/indra/util/named_query.py b/linden/indra/lib/python/indra/util/named_query.py new file mode 100644 index 0000000..019eb63 --- /dev/null +++ b/linden/indra/lib/python/indra/util/named_query.py | |||
@@ -0,0 +1,215 @@ | |||
1 | """\ | ||
2 | @file named_query.py | ||
3 | @author Ryan Williams, Phoenix | ||
4 | @date 2007-07-31 | ||
5 | @brief An API for running named queries. | ||
6 | |||
7 | $LicenseInfo:firstyear=2007&license=mit$ | ||
8 | |||
9 | Copyright (c) 2007, Linden Research, Inc. | ||
10 | |||
11 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
12 | of this software and associated documentation files (the "Software"), to deal | ||
13 | in the Software without restriction, including without limitation the rights | ||
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
15 | copies of the Software, and to permit persons to whom the Software is | ||
16 | furnished to do so, subject to the following conditions: | ||
17 | |||
18 | The above copyright notice and this permission notice shall be included in | ||
19 | all copies or substantial portions of the Software. | ||
20 | |||
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
27 | THE SOFTWARE. | ||
28 | $/LicenseInfo$ | ||
29 | """ | ||
30 | |||
31 | import MySQLdb | ||
32 | import os | ||
33 | import os.path | ||
34 | import time | ||
35 | |||
36 | from indra.base import llsd | ||
37 | from indra.base import config | ||
38 | from indra.ipc import russ | ||
39 | |||
40 | _g_named_manager = None | ||
41 | |||
42 | # this function is entirely intended for testing purposes, | ||
43 | # because it's tricky to control the config from inside a test | ||
44 | def _init_g_named_manager(sql_dir = None): | ||
45 | if sql_dir is None: | ||
46 | sql_dir = config.get('named-query-base-dir') | ||
47 | global _g_named_manager | ||
48 | _g_named_manager = NamedQueryManager( | ||
49 | os.path.abspath(os.path.realpath(sql_dir))) | ||
50 | |||
51 | def get(name): | ||
52 | "@brief get the named query object to be used to perform queries" | ||
53 | if _g_named_manager is None: | ||
54 | _init_g_named_manager() | ||
55 | return _g_named_manager.get(name) | ||
56 | |||
57 | def sql(name, params): | ||
58 | # use module-global NamedQuery object to perform default substitution | ||
59 | return get(name).sql(params) | ||
60 | |||
61 | def run(connection, name, params, expect_rows = None): | ||
62 | """\ | ||
63 | @brief given a connection, run a named query with the params | ||
64 | |||
65 | Note that this function will fetch ALL rows. | ||
66 | @param connection The connection to use | ||
67 | @param name The name of the query to run | ||
68 | @param params The parameters passed into the query | ||
69 | @param expect_rows The number of rows expected. Set to 1 if return_as_map is true. Raises ExpectationFailed if the number of returned rows doesn't exactly match. Kind of a hack. | ||
70 | @return Returns the result set as a list of dicts. | ||
71 | """ | ||
72 | return get(name).run(connection, params, expect_rows) | ||
73 | |||
74 | class ExpectationFailed(Exception): | ||
75 | def __init__(self, message): | ||
76 | self.message = message | ||
77 | |||
78 | class NamedQuery(object): | ||
79 | def __init__(self, name, filename): | ||
80 | self._stat_interval = 5000 # 5 seconds | ||
81 | self._name = name | ||
82 | self._location = filename | ||
83 | self.load_contents() | ||
84 | |||
85 | def name(self): | ||
86 | return self._name | ||
87 | |||
88 | def get_modtime(self): | ||
89 | return os.path.getmtime(self._location) | ||
90 | |||
91 | def load_contents(self): | ||
92 | self._contents = llsd.parse(open(self._location).read()) | ||
93 | self._ttl = int(self._contents.get('ttl', 0)) | ||
94 | self._return_as_map = bool(self._contents.get('return_as_map', False)) | ||
95 | self._legacy_dbname = self._contents.get('legacy_dbname', None) | ||
96 | self._legacy_query = self._contents.get('legacy_query', None) | ||
97 | self._options = self._contents.get('options', {}) | ||
98 | self._base_query = self._contents['base_query'] | ||
99 | |||
100 | self._last_mod_time = self.get_modtime() | ||
101 | self._last_check_time = time.time() | ||
102 | |||
103 | def ttl(self): | ||
104 | return self._ttl | ||
105 | |||
106 | def legacy_dbname(self): | ||
107 | return self._legacy_dbname | ||
108 | |||
109 | def legacy_query(self): | ||
110 | return self._legacy_query | ||
111 | |||
112 | def return_as_map(self): | ||
113 | return self._return_as_map | ||
114 | |||
115 | def run(self, connection, params, expect_rows = None, use_dictcursor = True): | ||
116 | """\ | ||
117 | @brief given a connection, run a named query with the params | ||
118 | |||
119 | Note that this function will fetch ALL rows. We do this because it | ||
120 | opens and closes the cursor to generate the values, and this isn't a generator so the | ||
121 | cursor has no life beyond the method call. | ||
122 | @param cursor The connection to use (this generates its own cursor for the query) | ||
123 | @param name The name of the query to run | ||
124 | @param params The parameters passed into the query | ||
125 | @param expect_rows The number of rows expected. Set to 1 if return_as_map is true. Raises ExpectationFailed if the number of returned rows doesn't exactly match. Kind of a hack. | ||
126 | @param use_dictcursor Set to false to use a normal cursor and manually convert the rows to dicts. | ||
127 | @return Returns the result set as a list of dicts, or, if the named query has return_as_map set to true, returns a single dict. | ||
128 | """ | ||
129 | if use_dictcursor: | ||
130 | cursor = connection.cursor(MySQLdb.cursors.DictCursor) | ||
131 | else: | ||
132 | cursor = connection.cursor() | ||
133 | |||
134 | statement = self.sql(params) | ||
135 | #print "SQL:", statement | ||
136 | rows = cursor.execute(statement) | ||
137 | |||
138 | # *NOTE: the expect_rows argument is a very cheesy way to get some | ||
139 | # validation on the result set. If you want to add more expectation | ||
140 | # logic, do something more object-oriented and flexible. Or use an ORM. | ||
141 | if(self._return_as_map): | ||
142 | expect_rows = 1 | ||
143 | if expect_rows is not None and rows != expect_rows: | ||
144 | cursor.close() | ||
145 | raise ExpectationFailed("Statement expected %s rows, got %s. Sql: %s" % ( | ||
146 | expect_rows, rows, statement)) | ||
147 | |||
148 | # convert to dicts manually if we're not using a dictcursor | ||
149 | if use_dictcursor: | ||
150 | result_set = cursor.fetchall() | ||
151 | else: | ||
152 | if cursor.description is None: | ||
153 | # an insert or something | ||
154 | x = cursor.fetchall() | ||
155 | cursor.close() | ||
156 | return x | ||
157 | |||
158 | names = [x[0] for x in cursor.description] | ||
159 | |||
160 | result_set = [] | ||
161 | for row in cursor.fetchall(): | ||
162 | converted_row = {} | ||
163 | for idx, col_name in enumerate(names): | ||
164 | converted_row[col_name] = row[idx] | ||
165 | result_set.append(converted_row) | ||
166 | |||
167 | cursor.close() | ||
168 | if self._return_as_map: | ||
169 | return result_set[0] | ||
170 | return result_set | ||
171 | |||
172 | def sql(self, params): | ||
173 | self.refresh() | ||
174 | |||
175 | # build the query from the options available and the params | ||
176 | base_query = [] | ||
177 | base_query.append(self._base_query) | ||
178 | for opt, extra_where in self._options.items(): | ||
179 | if opt in params and (params[opt] == 0 or params[opt]): | ||
180 | if type(extra_where) in (dict, list, tuple): | ||
181 | base_query.append(extra_where[params[opt]]) | ||
182 | else: | ||
183 | base_query.append(extra_where) | ||
184 | |||
185 | full_query = '\n'.join(base_query) | ||
186 | |||
187 | # do substitution | ||
188 | sql = russ.format(full_query, params) | ||
189 | return sql | ||
190 | |||
191 | def refresh(self): | ||
192 | # only stat the file every so often | ||
193 | now = time.time() | ||
194 | if(now - self._last_check_time > self._stat_interval): | ||
195 | self._last_check_time = now | ||
196 | modtime = self.get_modtime() | ||
197 | if(modtime > self._last_mod_time): | ||
198 | self.load_contents() | ||
199 | |||
200 | class NamedQueryManager(object): | ||
201 | def __init__(self, named_queries_dir): | ||
202 | self._dir = os.path.abspath(os.path.realpath(named_queries_dir)) | ||
203 | self._cached_queries = {} | ||
204 | |||
205 | def sql(self, name, params): | ||
206 | nq = self.get(name) | ||
207 | return nq.sql(params) | ||
208 | |||
209 | def get(self, name): | ||
210 | # new up/refresh a NamedQuery based on the name | ||
211 | nq = self._cached_queries.get(name) | ||
212 | if nq is None: | ||
213 | nq = NamedQuery(name, os.path.join(self._dir, name)) | ||
214 | self._cached_queries[name] = nq | ||
215 | return nq | ||