aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/lib
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:45:11 -0500
committerJacek Antonelli2008-08-15 23:45:11 -0500
commit215f423cbe18fe9ca14a26caef918d303bad28ff (patch)
tree0743442b286216cc8e19aa487c26f4e9345ffd64 /linden/indra/lib
parentSecond Life viewer sources 1.18.3.5-RC (diff)
downloadmeta-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')
-rw-r--r--linden/indra/lib/python/indra/__init__.py33
-rw-r--r--linden/indra/lib/python/indra/base/__init__.py27
-rw-r--r--linden/indra/lib/python/indra/base/config.py74
-rw-r--r--linden/indra/lib/python/indra/base/llsd.py870
-rw-r--r--linden/indra/lib/python/indra/base/lluuid.py290
-rw-r--r--linden/indra/lib/python/indra/ipc/__init__.py40
-rw-r--r--linden/indra/lib/python/indra/ipc/compatibility.py40
-rw-r--r--linden/indra/lib/python/indra/ipc/httputil.py9
-rw-r--r--linden/indra/lib/python/indra/ipc/llmessage.py40
-rw-r--r--linden/indra/lib/python/indra/ipc/llsdhttp.py84
-rw-r--r--linden/indra/lib/python/indra/ipc/mysql_pool.py103
-rw-r--r--linden/indra/lib/python/indra/ipc/russ.py157
-rw-r--r--linden/indra/lib/python/indra/ipc/saranwrap.py637
-rw-r--r--linden/indra/lib/python/indra/ipc/servicebuilder.py93
-rw-r--r--linden/indra/lib/python/indra/ipc/tokenstream.py40
-rw-r--r--linden/indra/lib/python/indra/ipc/webdav.py597
-rw-r--r--linden/indra/lib/python/indra/ipc/xml_rpc.py273
-rw-r--r--linden/indra/lib/python/indra/util/__init__.py40
-rw-r--r--linden/indra/lib/python/indra/util/helpformatter.py52
-rw-r--r--linden/indra/lib/python/indra/util/llmanifest.py40
-rw-r--r--linden/indra/lib/python/indra/util/llversion.py95
-rw-r--r--linden/indra/lib/python/indra/util/named_query.py215
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
5Copyright (c) 2006-2007, Linden Research, Inc. 7Copyright (c) 2006-2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
8# The source code in this file ("Source Code") is provided by Linden Lab 10this 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 11Agreement ("Agreement") previously entered between you and Linden
10# ("GPL"), unless you have obtained a separate licensing agreement 12Lab. By accessing, using, copying, modifying or distributing this
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13software, you acknowledge that you have been informed of your
12# the GPL can be found in doc/GPL-license.txt in this distribution, or 14obligations under the Agreement and agree to abide by those obligations.
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16ALL 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 17WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
16# it is applied to this Source Code. View the full text of the exception 18COMPLETENESS 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
7Copyright (c) 2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
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
7Copyright (c) 2006-2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29from os.path import dirname, join, realpath
30import types
31from indra.base import llsd
32
33_g_config_dict = None
34
35def 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
50def 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
66def 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
72def 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
7Copyright (c) 2006-2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29import datetime
30import base64
31import struct
32import time
33import types
34import re
35
36#from cElementTree import fromstring ## This does not work under Windows
37try:
38 ## This is the old name of elementtree, for use with 2.3
39 from elementtree.ElementTree import fromstring
40except ImportError:
41 ## This is the name of elementtree under python 2.5
42 from xml.etree.ElementTree import fromstring
43
44from indra.base import lluuid
45
46int_regex = re.compile("[-+]?\d+")
47real_regex = re.compile("[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?")
48alpha_regex = re.compile("[a-zA-Z]+")
49date_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
52class LLSDParseError(Exception):
53 pass
54
55class LLSDSerializationError(Exception):
56 pass
57
58
59class binary(str):
60 pass
61
62class uri(str):
63 pass
64
65
66BOOL_TRUE = ('1', '1.0', 'true')
67BOOL_FALSE = ('0', '0.0', 'false', '')
68
69
70def bool_to_python(node):
71 val = node.text or ''
72 if val in BOOL_TRUE:
73 return True
74 else:
75 return False
76
77def int_to_python(node):
78 val = node.text or ''
79 if not val.strip():
80 return 0
81 return int(val)
82
83def real_to_python(node):
84 val = node.text or ''
85 if not val.strip():
86 return 0.0
87 return float(val)
88
89def uuid_to_python(node):
90 return lluuid.UUID(node.text)
91
92def str_to_python(node):
93 return unicode(node.text or '').encode('utf8', 'replace')
94
95def bin_to_python(node):
96 return binary(base64.decodestring(node.text or ''))
97
98def 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
105def uri_to_python(node):
106 val = node.text or ''
107 if not val:
108 return None
109 return uri(val)
110
111def 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
117def array_to_python(node):
118 return [to_python(child) for child in node]
119
120
121NODE_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
135def to_python(node):
136 return NODE_HANDLERS[node.tag](node)
137
138class Nothing(object):
139 pass
140
141
142class 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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
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
218def format_xml(something):
219 return LLSDXMLFormatter().format(something)
220
221class 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
288def format_notation(something):
289 return LLSDNotationFormatter().format(something)
290
291def _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
299class 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
470class 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
762def format_binary(something):
763 return '<?llsd/binary?>\n' + _format_binary_recurse(something)
764
765def _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
810def 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
823class 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
836undef = LLSD(None)
837
838# register converters for stacked, if stacked is available
839try:
840 from mulib import stacked
841 stacked.NoProducer() # just to exercise stacked
842except:
843 print "Couldn't import mulib.stacked, not registering LLSD converters"
844else:
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
7Copyright (c) 2004-2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29import md5, random, socket, string, time, re
30
31def _int2binstr(i,l):
32 s=''
33 for a in range(l):
34 s=chr(i&0xFF)+s
35 i>>=8
36 return s
37
38def _binstr2int(s):
39 i = long(0)
40 for c in s:
41 i = (i<<8) + ord(c)
42 return i
43
44class 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
220def 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
231def 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
267def 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
276def uuid_bits_to_uuid(bits):
277 return UUID(uuid_bits_to_string(bits))
278
279
280try:
281 from mulib import stacked
282 stacked.NoProducer() # just to exercise stacked
283except:
284 print "Couldn't import mulib.stacked, not registering UUID converter"
285else:
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
5Copyright (c) 2006-2007, Linden Research, Inc. 7Copyright (c) 2006-2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9Permission 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 10of 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 11in the Software without restriction, including without limitation the rights
10# ("GPL"), unless you have obtained a separate licensing agreement 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13copies 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 14furnished to do so, subject to the following conditions:
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16The 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 17all 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 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# online at http://secondlife.com/developers/opensource/flossexception 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# By copying, modifying or distributing this software, you acknowledge 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# that you have read and understood your obligations described above, 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# and agree to abide by those obligations. 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# 25THE 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
5Copyright (c) 2007, Linden Research, Inc. 7Copyright (c) 2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9Permission 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 10of 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 11in the Software without restriction, including without limitation the rights
10# ("GPL"), unless you have obtained a separate licensing agreement 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13copies 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 14furnished to do so, subject to the following conditions:
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16The 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 17all 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 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# online at http://secondlife.com/developers/opensource/flossexception 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# By copying, modifying or distributing this software, you acknowledge 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# that you have read and understood your obligations described above, 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# and agree to abide by those obligations. 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# 25THE 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
2import warnings
3
4warnings.warn("indra.ipc.httputil has been deprecated; use eventlet.httpc instead", DeprecationWarning, 2)
5
6from eventlet.httpc import *
7
8
9makeConnection = 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
5Copyright (c) 2007, Linden Research, Inc. 7Copyright (c) 2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9Permission 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 10of 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 11in the Software without restriction, including without limitation the rights
10# ("GPL"), unless you have obtained a separate licensing agreement 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13copies 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 14furnished to do so, subject to the following conditions:
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16The 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 17all 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 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# online at http://secondlife.com/developers/opensource/flossexception 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# By copying, modifying or distributing this software, you acknowledge 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# that you have read and understood your obligations described above, 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# and agree to abide by those obligations. 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# 25THE 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
29from sets import Set, ImmutableSet 29from 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
7Copyright (c) 2006-2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29import os.path
30import os
31import urlparse
32
33from indra.base import llsd
34
35from eventlet import httpc
36
37
38get, put, delete, post = httpc.make_suite(
39 llsd.format_xml, llsd.parse, 'application/xml+llsd')
40
41
42for x in (httpc.ConnectionError, httpc.NotFound, httpc.Forbidden):
43 globals()[x.__name__] = x
44
45
46def 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
54def getStatus(url, use_proxy=False):
55 status, _headers, _body = get(url, use_proxy=use_proxy, verbose=True)
56 return status
57
58
59def putStatus(url, data):
60 status, _headers, _body = put(url, data, verbose=True)
61 return status
62
63
64def deleteStatus(url):
65 status, _headers, _body = delete(url, verbose=True)
66 return status
67
68
69def postStatus(url, data):
70 status, _headers, _body = post(url, data, verbose=True)
71 return status
72
73
74def postFileStatus(url, filename):
75 status, _headers, body = postFile(url, filename, verbose=True)
76 return status, body
77
78
79def getFromSimulator(path, use_proxy=False):
80 return get('http://' + simulatorHostAndPort + path, use_proxy=use_proxy)
81
82
83def 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
7Copyright (c) 2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29import os
30
31from eventlet.pools import Pool
32from eventlet.processes import DeadProcess
33from indra.ipc import saranwrap
34
35import 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
45class DatabaseConnector(object):
46 """\
47@brief This is an object which will maintain a collection of database
48connection pools keyed on host,databasename"""
49 def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs):
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
80class 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
6Many details on how this should work is available on the wiki:
7https://wiki.secondlife.com/wiki/Recursive_URL_Substitution_Syntax
8
9Adding features to this should be reflected in that page in the
10implementations section.
11
12$LicenseInfo:firstyear=2007&license=mit$
13
14Copyright (c) 2007, Linden Research, Inc.
15
16Permission is hereby granted, free of charge, to any person obtaining a copy
17of this software and associated documentation files (the "Software"), to deal
18in the Software without restriction, including without limitation the rights
19to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20copies of the Software, and to permit persons to whom the Software is
21furnished to do so, subject to the following conditions:
22
23The above copyright notice and this permission notice shall be included in
24all copies or substantial portions of the Software.
25
26THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32THE SOFTWARE.
33$/LicenseInfo$
34"""
35
36import urllib
37from indra.ipc import llsdhttp
38
39class UnbalancedBraces(Exception):
40 pass
41
42class UnknownDirective(Exception):
43 pass
44
45class BadDirective(Exception):
46 pass
47
48def 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
60def 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
111def _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
132def _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
143def _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
151def _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
6objects and callables.
7
8$LicenseInfo:firstyear=2007&license=mit$
9
10Copyright (c) 2007, Linden Research, Inc.
11
12Permission is hereby granted, free of charge, to any person obtaining a copy
13of this software and associated documentation files (the "Software"), to deal
14in the Software without restriction, including without limitation the rights
15to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16copies of the Software, and to permit persons to whom the Software is
17furnished to do so, subject to the following conditions:
18
19The above copyright notice and this permission notice shall be included in
20all copies or substantial portions of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28THE SOFTWARE.
29$/LicenseInfo$
30
31This file provides classes and exceptions used for simple python level
32remote procedure calls. This is achieved by intercepting the basic
33getattr and setattr calls in a client proxy, which commnicates those
34down to the server which will dispatch them to objects in it's process
35space.
36
37The basic protocol to get and set attributes is for the client proxy
38to issue the command:
39
40getattr $id $name
41setattr $id $name $value
42
43getitem $id $item
44setitem $id $item $value
45eq $id $rhs
46del $id
47
48When the get returns a callable, the client proxy will provide a
49callable proxy which will invoke a remote procedure call. The command
50issued from the callable proxy to server is:
51
52call $id $name $args $kwargs
53
54If the client supplies an id of None, then the get/set/call is applied
55to the object(s) exported from the server.
56
57The server will parse the get/set/call, take the action indicated, and
58return back to the caller one of:
59
60value $val
61callable
62object $id
63exception $excp
64
65To handle object expiration, the proxy will instruct the rpc server to
66discard objects which are no longer in use. This is handled by
67catching proxy deletion and sending the command:
68
69del $id
70
71The server will handle this by removing clearing it's own internal
72references. This does not mean that the object will necessarily be
73cleaned from the server, but no artificial references will remain
74after successfully completing. On completion, the server will return
75one of:
76
77value None
78exception $excp
79
80The server also accepts a special command for debugging purposes:
81
82status
83
84Which will be intercepted by the server to write back:
85
86status {...}
87
88The wire protocol is to pickle the Request class in this file. The
89request class is basically an action and a map of parameters'
90"""
91
92import os
93import cPickle
94import struct
95import sys
96
97try:
98 set = set
99 frozenset = frozenset
100except NameError:
101 from sets import Set as set, ImmutableSet as frozenset
102
103from eventlet.processes import Process
104from eventlet import api
105
106#
107# debugging hooks
108#
109_g_debug_mode = False
110if _g_debug_mode:
111 import traceback
112
113
114def 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
127def 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
140def 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
147class 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
152class 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
157class UnrecoverableError(Exception):
158 pass
159
160class 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
172def _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
178def _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
199def _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
205def _write_request(param, output):
206 _prnt("request: %s" % param)
207 str = cPickle.dumps(param)
208 _write_lp_hunk(output, str)
209
210def _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
218def _prnt(message):
219 global _g_debug_mode
220 if _g_debug_mode:
221 print message
222
223_g_logfile = None
224def _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
231def _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
242class Proxy(object):
243 """\
244@class Proxy
245@brief This class wraps a remote python process, presumably available
246in an instance of an Server.
247
248This is the class you will typically use as a client to a child
249process. Simply instantiate one around a file-like interface and start
250calling methods on the thing that is exported. The dir() builtin is
251not 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
310class ObjectProxy(Proxy):
311 """\
312@class ObjectProxy
313@brief This class wraps a remote object in the Server
314
315This class will be created during normal operation, and users should
316not 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
372def 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
385class CallableProxy(object):
386 """\
387@class CallableProxy
388@brief This class wraps a remote function in the Server
389
390This class will be created by an Proxy during normal operation,
391and 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
412class 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
418when 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
577def raise_a_weird_error():
578 raise "oh noes you can raise a string"
579
580# test function used for testing return of unpicklable exceptions
581def raise_an_unpicklable_error():
582 class Unpicklable(Exception):
583 pass
584 raise Unpicklable()
585
586# test function used for testing return of picklable exceptions
587def raise_standard_error():
588 raise FloatingPointError()
589
590# test function to make sure print doesn't break the wrapper
591def print_string(str):
592 print str
593
594# test function to make sure printing on stdout doesn't break the
595# wrapper
596def err_string(str):
597 print >>sys.stderr, str
598
599def 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
636if __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
8Copyright (c) 2007, Linden Research, Inc.
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in
18all copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26THE SOFTWARE.
27$/LicenseInfo$
28"""
29
30from indra.base import config
31from indra.ipc import llsdhttp
32from indra.ipc import russ
33
34# *NOTE: agent presence relies on this variable existing and being current, it is a huge hack
35services_config = {}
36try:
37 services_config = llsdhttp.get(config.get('services-config'))
38except:
39 pass
40
41_g_builder = None
42def 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
61class 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
5Copyright (c) 2007, Linden Research, Inc. 7Copyright (c) 2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9Permission 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 10of 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 11in the Software without restriction, including without limitation the rights
10# ("GPL"), unless you have obtained a separate licensing agreement 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13copies 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 14furnished to do so, subject to the following conditions:
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16The 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 17all 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 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# online at http://secondlife.com/developers/opensource/flossexception 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# By copying, modifying or distributing this software, you acknowledge 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# that you have read and understood your obligations described above, 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# and agree to abide by those obligations. 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# 25THE 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
29import re 29import 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
7Copyright (c) 2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29import sys, os, httplib, urlparse
30import socket, time
31import xml.dom.minidom
32import syslog
33# import signal
34
35__revision__ = '0'
36
37dav_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
47class 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
63class Timeout (Exception):
64 """ Timeout docstring """
65 def __init__ (self, arg=''):
66 Exception.__init__ (self, arg)
67
68
69def alarm_handler (signum, frame):
70 """ alarm_handler docstring """
71 raise Timeout ('caught alarm')
72
73
74class 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
582def 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
591def 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
7Copyright (c) 2006-2007, Linden Research, Inc.
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in
17all copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25THE SOFTWARE.
26$/LicenseInfo$
27"""
28
29
30from greenlet import greenlet
31
32from mulib import mu
33
34from xml.sax import handler
35from xml.sax import parseString
36
37
38# States
39class 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
51class START(Expected):
52 pass
53
54
55class END(Expected):
56 pass
57
58
59class STR(object):
60 tag = ''
61
62
63START = START('')
64END = END('')
65
66
67class Malformed(Exception):
68 pass
69
70
71class 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
120def 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
133def 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
163def 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
212VALUE = mu.tag_factory('value')
213BOOLEAN = mu.tag_factory('boolean')
214INT = mu.tag_factory('int')
215STRUCT = mu.tag_factory('struct')
216MEMBER = mu.tag_factory('member')
217NAME = mu.tag_factory('name')
218ARRAY = mu.tag_factory('array')
219DATA = mu.tag_factory('data')
220STRING = mu.tag_factory('string')
221DOUBLE = mu.tag_factory('double')
222METHODRESPONSE = mu.tag_factory('methodResponse')
223PARAMS = mu.tag_factory('params')
224PARAM = mu.tag_factory('param')
225
226mu.inline_elements['string'] = True
227mu.inline_elements['boolean'] = True
228mu.inline_elements['name'] = True
229
230
231def _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
255def generate(*args):
256 params = PARAMS()
257 for arg in args:
258 params[PARAM[_generate(arg)]]
259 return METHODRESPONSE[params]
260
261
262if __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
5Copyright (c) 2006-2007, Linden Research, Inc. 7Copyright (c) 2006-2007, Linden Research, Inc.
6 8
7# Second Life Viewer Source Code 9Permission 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 10of 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 11in the Software without restriction, including without limitation the rights
10# ("GPL"), unless you have obtained a separate licensing agreement 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# ("Other License"), formally executed by you and Linden Lab. Terms of 13copies 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 14furnished to do so, subject to the following conditions:
13# online at http://secondlife.com/developers/opensource/gplv2 15
14# 16The 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 17all 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 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# online at http://secondlife.com/developers/opensource/flossexception 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# By copying, modifying or distributing this software, you acknowledge 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# that you have read and understood your obligations described above, 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# and agree to abide by those obligations. 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# 25THE 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
8Copyright (c) 2007, Linden Research, Inc.
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in
18all copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26THE SOFTWARE.
27$/LicenseInfo$
28"""
29
30import optparse
31import textwrap
32
33class 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
6Copyright (c) 2007, Linden Research, Inc. 8Copyright (c) 2007, Linden Research, Inc.
7 9
8# Second Life Viewer Source Code 10Permission 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 11of 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 12in the Software without restriction, including without limitation the rights
11# ("GPL"), unless you have obtained a separate licensing agreement 13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12# ("Other License"), formally executed by you and Linden Lab. Terms of 14copies 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 15furnished to do so, subject to the following conditions:
14# online at http://secondlife.com/developers/opensource/gplv2 16
15# 17The 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 18all 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 20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# online at http://secondlife.com/developers/opensource/flossexception 21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# 22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21# By copying, modifying or distributing this software, you acknowledge 23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# that you have read and understood your obligations described above, 24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# and agree to abide by those obligations. 25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24# 26THE 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
30import commands 30import 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
8Copyright (c) 2006-2007, Linden Research, Inc.
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in
18all copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26THE SOFTWARE.
27$/LicenseInfo$
28"""
29
30import re, sys, os, commands
31
32# Methods for gathering version information from
33# llversionviewer.h and llversionserver.h
34
35def 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
39def 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
46def 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
59def 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
64def get_viewer_version():
65 return get_version('viewer')
66
67def get_server_version():
68 return get_version('server')
69
70def get_viewer_channel():
71 return get_channel('viewer')
72
73def get_server_channel():
74 return get_channel('server')
75
76# Methods for gathering subversion information
77def 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
87def get_svn_branch():
88 branch_re = re.compile('URL: (\S+)')
89 return get_svn_status_matching(branch_re)
90
91def 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
9Copyright (c) 2007, Linden Research, Inc.
10
11Permission is hereby granted, free of charge, to any person obtaining a copy
12of this software and associated documentation files (the "Software"), to deal
13in the Software without restriction, including without limitation the rights
14to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15copies of the Software, and to permit persons to whom the Software is
16furnished to do so, subject to the following conditions:
17
18The above copyright notice and this permission notice shall be included in
19all copies or substantial portions of the Software.
20
21THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27THE SOFTWARE.
28$/LicenseInfo$
29"""
30
31import MySQLdb
32import os
33import os.path
34import time
35
36from indra.base import llsd
37from indra.base import config
38from 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
44def _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
51def 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
57def sql(name, params):
58 # use module-global NamedQuery object to perform default substitution
59 return get(name).sql(params)
60
61def run(connection, name, params, expect_rows = None):
62 """\
63@brief given a connection, run a named query with the params
64
65Note 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
74class ExpectationFailed(Exception):
75 def __init__(self, message):
76 self.message = message
77
78class 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
119Note that this function will fetch ALL rows. We do this because it
120opens and closes the cursor to generate the values, and this isn't a generator so the
121cursor 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
200class 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