summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Goetze <cg@lindenlab.com>2007-12-01 01:29:28 +0000
committerChristian Goetze <cg@lindenlab.com>2007-12-01 01:29:28 +0000
commit7b7dd4e6f61aa731ef7f9f255014f5b76d5b9fec (patch)
tree08bf4cb9354f975940b83cd695a2aa8bb8d74870
parent5c53c187fff573ef73a643949b3bded1084b0983 (diff)
svn merge -r74808:74832 svn+ssh://svn/svn/linden/branches/qa-dpo-9a
-rw-r--r--etc/message.xml2
-rw-r--r--indra/lib/python/indra/base/lluuid.py4
-rw-r--r--indra/lib/python/indra/ipc/mysql_pool.py2
-rw-r--r--indra/lib/python/indra/util/named_query.py387
-rw-r--r--indra/llcommon/llsd.cpp39
-rw-r--r--indra/llcommon/llsd.h10
-rw-r--r--indra/llmessage/llservicebuilder.cpp99
-rw-r--r--indra/llmessage/llurlrequest.cpp1
-rw-r--r--indra/llmessage/llurlrequest.h2
-rw-r--r--indra/llmessage/message.cpp11
-rw-r--r--indra/llmessage/message.h7
-rw-r--r--indra/newview/llstartup.cpp2
-rw-r--r--indra/newview/llviewermessage.cpp36
-rw-r--r--indra/newview/llviewermessage.h2
-rw-r--r--indra/newview/llviewerprecompiledheaders.h1
-rw-r--r--indra/test/llservicebuilder_tut.cpp61
16 files changed, 577 insertions, 89 deletions
diff --git a/etc/message.xml b/etc/message.xml
index 1e6e30ec6c..18f61198ce 100644
--- a/etc/message.xml
+++ b/etc/message.xml
@@ -362,7 +362,7 @@
<boolean>false</boolean>
</map>
- <key>avatarpickrequest</key>
+ <key>avatarpicksrequest</key>
<map>
<key>service_name</key>
<string>avatar-pick</string>
diff --git a/indra/lib/python/indra/base/lluuid.py b/indra/lib/python/indra/base/lluuid.py
index eb0c557b04..609c8cc261 100644
--- a/indra/lib/python/indra/base/lluuid.py
+++ b/indra/lib/python/indra/base/lluuid.py
@@ -212,6 +212,10 @@ class UUID(object):
_int2binstr(v3,4) + \
_int2binstr(v4,4)
+
+# module-level null constant
+NULL = UUID()
+
def printTranslatedMemory(four_hex_uints):
"""
We expect to get the string as four hex units. eg:
diff --git a/indra/lib/python/indra/ipc/mysql_pool.py b/indra/lib/python/indra/ipc/mysql_pool.py
index 4a265a1f2e..6288a4c732 100644
--- a/indra/lib/python/indra/ipc/mysql_pool.py
+++ b/indra/lib/python/indra/ipc/mysql_pool.py
@@ -97,7 +97,7 @@ class ConnectionPool(Pool):
except (AttributeError, DeadProcess), e:
conn = self.create()
# TODO figure out if we're still connected to the database
- if conn:
+ if conn is not None:
Pool.put(self, conn)
else:
self.current_size -= 1
diff --git a/indra/lib/python/indra/util/named_query.py b/indra/lib/python/indra/util/named_query.py
index 019eb6306b..c3bdd046fd 100644
--- a/indra/lib/python/indra/util/named_query.py
+++ b/indra/lib/python/indra/util/named_query.py
@@ -28,20 +28,33 @@ THE SOFTWARE.
$/LicenseInfo$
"""
+import errno
import MySQLdb
+import MySQLdb.cursors
import os
import os.path
+import re
import time
+#import sys # *TODO: remove. only used in testing.
+#import pprint # *TODO: remove. only used in testing.
+
+try:
+ set = set
+except NameError:
+ from sets import Set as set
+
from indra.base import llsd
from indra.base import config
-from indra.ipc import russ
_g_named_manager = None
-# this function is entirely intended for testing purposes,
-# because it's tricky to control the config from inside a test
def _init_g_named_manager(sql_dir = None):
+ """Initializes a global NamedManager object to point at a
+ specified named queries hierarchy.
+
+ This function is intended entirely for testing purposes,
+ because it's tricky to control the config from inside a test."""
if sql_dir is None:
sql_dir = config.get('named-query-base-dir')
global _g_named_manager
@@ -49,14 +62,14 @@ def _init_g_named_manager(sql_dir = None):
os.path.abspath(os.path.realpath(sql_dir)))
def get(name):
- "@brief get the named query object to be used to perform queries"
+ "Get the named query object to be used to perform queries"
if _g_named_manager is None:
_init_g_named_manager()
return _g_named_manager.get(name)
-def sql(name, params):
+def sql(connection, name, params):
# use module-global NamedQuery object to perform default substitution
- return get(name).sql(params)
+ return get(name).sql(connection, params)
def run(connection, name, params, expect_rows = None):
"""\
@@ -72,66 +85,243 @@ Note that this function will fetch ALL rows.
return get(name).run(connection, params, expect_rows)
class ExpectationFailed(Exception):
+ """ Exception that is raised when an expectation for an sql query
+ is not met."""
def __init__(self, message):
+ Exception.__init__(self, message)
self.message = message
class NamedQuery(object):
def __init__(self, name, filename):
- self._stat_interval = 5000 # 5 seconds
+ """ Construct a NamedQuery object. The name argument is an
+ arbitrary name as a handle for the query, and the filename is
+ a path to a file containing an llsd named query document."""
+ self._stat_interval_seconds = 5 # 5 seconds
self._name = name
self._location = filename
+ self._alternative = dict()
+ self._last_mod_time = 0
+ self._last_check_time = 0
+ self.deleted = False
self.load_contents()
def name(self):
+ """ The name of the query. """
return self._name
def get_modtime(self):
- return os.path.getmtime(self._location)
+ """ Returns the mtime (last modified time) of the named query
+ file, if such exists."""
+ if self._location:
+ return os.path.getmtime(self._location)
+ return 0
def load_contents(self):
- self._contents = llsd.parse(open(self._location).read())
+ """ Loads and parses the named query file into self. Does
+ nothing if self.location is nonexistant."""
+ if self._location:
+ self._reference_contents(llsd.parse(open(self._location).read()))
+ # Check for alternative implementations
+ try:
+ for name, alt in self._contents['alternative'].items():
+ nq = NamedQuery(name, None)
+ nq._reference_contents(alt)
+ self._alternative[name] = nq
+ except KeyError, e:
+ pass
+ self._last_mod_time = self.get_modtime()
+ self._last_check_time = time.time()
+
+ def _reference_contents(self, contents):
+ "Helper method which builds internal structure from parsed contents"
+ self._contents = contents
self._ttl = int(self._contents.get('ttl', 0))
self._return_as_map = bool(self._contents.get('return_as_map', False))
self._legacy_dbname = self._contents.get('legacy_dbname', None)
- self._legacy_query = self._contents.get('legacy_query', None)
- self._options = self._contents.get('options', {})
- self._base_query = self._contents['base_query']
- self._last_mod_time = self.get_modtime()
- self._last_check_time = time.time()
+ # reset these before doing the sql conversion because we will
+ # read them there. reset these while loading so we pick up
+ # changes.
+ self._around = set()
+ self._append = set()
+ self._integer = set()
+ self._options = self._contents.get('dynamic_where', {})
+ for key in self._options:
+ if isinstance(self._options[key], basestring):
+ self._options[key] = self._convert_sql(self._options[key])
+ elif isinstance(self._options[key], list):
+ lines = []
+ for line in self._options[key]:
+ lines.append(self._convert_sql(line))
+ self._options[key] = lines
+ else:
+ moreopt = {}
+ for kk in self._options[key]:
+ moreopt[kk] = self._convert_sql(self._options[key][kk])
+ self._options[key] = moreopt
+ self._base_query = self._convert_sql(self._contents['base_query'])
+ self._query_suffix = self._convert_sql(
+ self._contents.get('query_suffix', ''))
+
+ def _convert_sql(self, sql):
+ """convert the parsed sql into a useful internal structure.
+
+ This function has to turn the named query format into a pyformat
+ style. It also has to look for %:name% and :name% and
+ ready them for use in LIKE statements"""
+ if sql:
+ #print >>sys.stderr, "sql:",sql
+ expr = re.compile("(%?):([a-zA-Z][a-zA-Z0-9_-]*)%")
+ sql = expr.sub(self._prepare_like, sql)
+ expr = re.compile("#:([a-zA-Z][a-zA-Z0-9_-]*)")
+ sql = expr.sub(self._prepare_integer, sql)
+ expr = re.compile(":([a-zA-Z][a-zA-Z0-9_-]*)")
+ sql = expr.sub("%(\\1)s", sql)
+ return sql
+
+ def _prepare_like(self, match):
+ """This function changes LIKE statement replace behavior
+
+ It works by turning %:name% to %(_name_around)s and :name% to
+ %(_name_append)s. Since a leading '_' is not a valid keyname
+ input (enforced via unit tests), it will never clash with
+ existing keys. Then, when building the statement, the query
+ runner will generate corrected strings."""
+ if match.group(1) == '%':
+ # there is a leading % so this is treated as prefix/suffix
+ self._around.add(match.group(2))
+ return "%(" + self._build_around_key(match.group(2)) + ")s"
+ else:
+ # there is no leading %, so this is suffix only
+ self._append.add(match.group(2))
+ return "%(" + self._build_append_key(match.group(2)) + ")s"
+
+ def _build_around_key(self, key):
+ return "_" + key + "_around"
+
+ def _build_append_key(self, key):
+ return "_" + key + "_append"
+
+ def _prepare_integer(self, match):
+ """This function adjusts the sql for #:name replacements
+
+ It works by turning #:name to %(_name_as_integer)s. Since a
+ leading '_' is not a valid keyname input (enforced via unit
+ tests), it will never clash with existing keys. Then, when
+ building the statement, the query runner will generate
+ corrected strings."""
+ self._integer.add(match.group(1))
+ return "%(" + self._build_integer_key(match.group(1)) + ")s"
+
+ def _build_integer_key(self, key):
+ return "_" + key + "_as_integer"
+
+ def _strip_wildcards_to_list(self, value):
+ """Take string, and strip out the LIKE special characters.
+
+ Technically, this is database dependant, but postgresql and
+ mysql use the same wildcards, and I am not aware of a general
+ way to handle this. I think you need a sql statement of the
+ form:
+
+ LIKE_STRING( [ANY,ONE,str]... )
+
+ which would treat ANY as their any string, and ONE as their
+ single glyph, and str as something that needs database
+ specific encoding to not allow any % or _ to affect the query.
+
+ As it stands, I believe it's impossible to write a named query
+ style interface which uses like to search the entire space of
+ text available. Imagine the query:
+
+ % of brain used by average linden
+
+ In order to search for %, it must be escaped, so once you have
+ escaped the string to not do wildcard searches, and be escaped
+ for the database, and then prepended the wildcard you come
+ back with one of:
+
+ 1) %\% of brain used by average linden
+ 2) %%% of brain used by average linden
+
+ Then, when passed to the database to be escaped to be database
+ safe, you get back:
+
+ 1) %\\% of brain used by average linden
+ : which means search for any character sequence, followed by a
+ backslash, followed by any sequence, followed by ' of
+ brain...'
+ 2) %%% of brain used by average linden
+ : which (I believe) means search for a % followed by any
+ character sequence followed by 'of brain...'
+
+ Neither of which is what we want!
+
+ So, we need a vendor (or extention) for LIKE_STRING. Anyone
+ want to write it?"""
+ utf8_value = unicode(value, "utf-8")
+ esc_list = []
+ remove_chars = set(u"%_")
+ for glyph in utf8_value:
+ if glyph in remove_chars:
+ continue
+ esc_list.append(glyph.encode("utf-8"))
+ return esc_list
+
+ def delete(self):
+ """ Makes this query unusable by deleting all the members and
+ setting the deleted member. This is desired when the on-disk
+ query has been deleted but the in-memory copy remains."""
+ # blow away all members except _name, _location, and deleted
+ name, location = self._name, self._location
+ for key in self.__dict__.keys():
+ del self.__dict__[key]
+ self.deleted = True
+ self._name, self._location = name, location
def ttl(self):
+ """ Estimated time to live of this query. Used for web
+ services to set the Expires header."""
return self._ttl
def legacy_dbname(self):
return self._legacy_dbname
- def legacy_query(self):
- return self._legacy_query
-
def return_as_map(self):
+ """ Returns true if this query is configured to return its
+ results as a single map (as opposed to a list of maps, the
+ normal behavior)."""
+
return self._return_as_map
- def run(self, connection, params, expect_rows = None, use_dictcursor = True):
- """\
-@brief given a connection, run a named query with the params
+ def for_schema(self, db_name):
+ "Look trough the alternates and return the correct query"
+ try:
+ return self._alternative[db_name]
+ except KeyError, e:
+ pass
+ return self
-Note that this function will fetch ALL rows. We do this because it
-opens and closes the cursor to generate the values, and this isn't a generator so the
-cursor has no life beyond the method call.
-@param cursor The connection to use (this generates its own cursor for the query)
-@param name The name of the query to run
-@param params The parameters passed into the query
-@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.
-@param use_dictcursor Set to false to use a normal cursor and manually convert the rows to dicts.
-@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.
+ def run(self, connection, params, expect_rows = None, use_dictcursor = True):
+ """given a connection, run a named query with the params
+
+ Note that this function will fetch ALL rows. We do this because it
+ opens and closes the cursor to generate the values, and this
+ isn't a generator so the cursor has no life beyond the method call.
+
+ @param cursor The connection to use (this generates its own cursor for the query)
+ @param name The name of the query to run
+ @param params The parameters passed into the query
+ @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.
+ @param use_dictcursor Set to false to use a normal cursor and manually convert the rows to dicts.
+ @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.
"""
if use_dictcursor:
cursor = connection.cursor(MySQLdb.cursors.DictCursor)
else:
cursor = connection.cursor()
- statement = self.sql(params)
+ statement = self.sql(connection, params)
#print "SQL:", statement
rows = cursor.execute(statement)
@@ -169,47 +359,152 @@ cursor has no life beyond the method call.
return result_set[0]
return result_set
- def sql(self, params):
+ def sql(self, connection, params):
+ """ Generates an SQL statement from the named query document
+ and a dictionary of parameters.
+
+ """
self.refresh()
# build the query from the options available and the params
base_query = []
base_query.append(self._base_query)
+ #print >>sys.stderr, "base_query:",base_query
for opt, extra_where in self._options.items():
- if opt in params and (params[opt] == 0 or params[opt]):
- if type(extra_where) in (dict, list, tuple):
+ if type(extra_where) in (dict, list, tuple):
+ if opt in params:
base_query.append(extra_where[params[opt]])
- else:
+ else:
+ if opt in params and params[opt]:
base_query.append(extra_where)
-
+ if self._query_suffix:
+ base_query.append(self._query_suffix)
+ #print >>sys.stderr, "base_query:",base_query
full_query = '\n'.join(base_query)
-
- # do substitution
- sql = russ.format(full_query, params)
+
+ # Go through the query and rewrite all of the ones with the
+ # @:name syntax.
+ rewrite = _RewriteQueryForArray(params)
+ expr = re.compile("@%\(([a-zA-Z][a-zA-Z0-9_-]*)\)s")
+ full_query = expr.sub(rewrite.operate, full_query)
+ params.update(rewrite.new_params)
+
+ # build out the params for like. We only have to do this
+ # parameters which were detected to have ued the where syntax
+ # during load.
+ #
+ # * treat the incoming string as utf-8
+ # * strip wildcards
+ # * append or prepend % as appropriate
+ new_params = {}
+ for key in params:
+ if key in self._around:
+ new_value = ['%']
+ new_value.extend(self._strip_wildcards_to_list(params[key]))
+ new_value.append('%')
+ new_params[self._build_around_key(key)] = ''.join(new_value)
+ if key in self._append:
+ new_value = self._strip_wildcards_to_list(params[key])
+ new_value.append('%')
+ new_params[self._build_append_key(key)] = ''.join(new_value)
+ if key in self._integer:
+ new_params[self._build_integer_key(key)] = int(params[key])
+ params.update(new_params)
+
+ # do substitution using the mysql (non-standard) 'literal'
+ # function to do the escaping.
+ sql = full_query % connection.literal(params)
return sql
def refresh(self):
- # only stat the file every so often
+ """ Refresh self from the file on the filesystem.
+
+ This is optimized to be callable as frequently as you wish,
+ without adding too much load. It does so by only stat-ing the
+ file every N seconds, where N defaults to 5 and is
+ configurable through the member _stat_interval_seconds. If the stat
+ reveals that the file has changed, refresh will re-parse the
+ contents of the file and use them to update the named query
+ instance. If the stat reveals that the file has been deleted,
+ refresh will call self.delete to make the in-memory
+ representation unusable."""
now = time.time()
- if(now - self._last_check_time > self._stat_interval):
+ if(now - self._last_check_time > self._stat_interval_seconds):
self._last_check_time = now
- modtime = self.get_modtime()
- if(modtime > self._last_mod_time):
- self.load_contents()
+ try:
+ modtime = self.get_modtime()
+ if(modtime > self._last_mod_time):
+ self.load_contents()
+ except OSError, e:
+ if e.errno == errno.ENOENT: # file not found
+ self.delete() # clean up self
+ raise # pass the exception along to the caller so they know that this query disappeared
class NamedQueryManager(object):
+ """ Manages the lifespan of NamedQuery objects, drawing from a
+ directory hierarchy of named query documents.
+
+ In practice this amounts to a memory cache of NamedQuery objects."""
+
def __init__(self, named_queries_dir):
+ """ Initializes a manager to look for named queries in a
+ directory."""
self._dir = os.path.abspath(os.path.realpath(named_queries_dir))
self._cached_queries = {}
- def sql(self, name, params):
+ def sql(self, connection, name, params):
nq = self.get(name)
- return nq.sql(params)
+ return nq.sql(connection, params)
def get(self, name):
- # new up/refresh a NamedQuery based on the name
+ """ Returns a NamedQuery instance based on the name, either
+ from memory cache, or by parsing from disk.
+
+ The name is simply a relative path to the directory associated
+ with the manager object. Before returning the instance, the
+ NamedQuery object is cached in memory, so that subsequent
+ accesses don't have to read from disk or do any parsing. This
+ means that NamedQuery objects returned by this method are
+ shared across all users of the manager object.
+ NamedQuery.refresh is used to bring the NamedQuery objects in
+ sync with the actual files on disk."""
nq = self._cached_queries.get(name)
if nq is None:
nq = NamedQuery(name, os.path.join(self._dir, name))
self._cached_queries[name] = nq
+ else:
+ try:
+ nq.refresh()
+ except OSError, e:
+ if e.errno == errno.ENOENT: # file not found
+ del self._cached_queries[name]
+ raise # pass exception along to caller so they know that the query disappeared
+
return nq
+
+class _RewriteQueryForArray(object):
+ "Helper class for rewriting queries with the @:name syntax"
+ def __init__(self, params):
+ self.params = params
+ self.new_params = dict()
+
+ def operate(self, match):
+ "Given a match, return the string that should be in use"
+ key = match.group(1)
+ value = self.params[key]
+ if type(value) in (list,tuple):
+ rv = []
+ for idx in range(len(value)):
+ new_key = "_" + key + "_" + str(idx)
+ self.new_params[new_key] = value[idx]
+ rv.append("%(" + new_key + ")s")
+ return ','.join(rv)
+ else:
+ # not something that can be expanded, so just drop the
+ # leading @ in the front of the match. This will mean that
+ # the single value we have, be it a string, int, whatever
+ # (other than dict) will correctly show up, eg:
+ #
+ # where foo in (@:foobar) -- foobar is a string, so we get
+ # where foo in (:foobar)
+ return match.group(0)[1:]
diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp
index 829ea25e38..ecca4c9b71 100644
--- a/indra/llcommon/llsd.cpp
+++ b/indra/llcommon/llsd.cpp
@@ -35,6 +35,7 @@
#include "llerror.h"
#include "../llmath/llmath.h"
#include "llformat.h"
+#include "llsdserialize.h"
#ifndef LL_RELEASE_FOR_DOWNLOAD
#define NAME_UNNAMED_NAMESPACE
@@ -765,6 +766,44 @@ const LLSD& LLSD::operator[](Integer i) const
U32 LLSD::allocationCount() { return Impl::sAllocationCount; }
U32 LLSD::outstandingCount() { return Impl::sOutstandingCount; }
+static const char *llsd_dump(const LLSD &llsd, bool useXMLFormat)
+{
+ // sStorage is used to hold the string representation of the llsd last
+ // passed into this function. If this function is never called (the
+ // normal case when not debugging), nothing is allocated. Otherwise
+ // sStorage will point to the result of the last call. This will actually
+ // be one leak, but since this is used only when running under the
+ // debugger, it should not be an issue.
+ static char *sStorage = NULL;
+ delete[] sStorage;
+ std::string out_string;
+ {
+ std::ostringstream out;
+ if (useXMLFormat)
+ out << LLSDXMLStreamer(llsd);
+ else
+ out << LLSDNotationStreamer(llsd);
+ out_string = out.str();
+ }
+ int len = out_string.length();
+ sStorage = new char[len + 1];
+ memcpy(sStorage, out_string.c_str(), len);
+ sStorage[len] = '\0';
+ return sStorage;
+}
+
+/// Returns XML version of llsd -- only to be called from debugger
+const char *LLSD::dumpXML(const LLSD &llsd)
+{
+ return llsd_dump(llsd, true);
+}
+
+/// Returns Notation version of llsd -- only to be called from debugger
+const char *LLSD::dump(const LLSD &llsd)
+{
+ return llsd_dump(llsd, false);
+}
+
LLSD::map_iterator LLSD::beginMap() { return makeMap(impl).beginMap(); }
LLSD::map_iterator LLSD::endMap() { return makeMap(impl).endMap(); }
LLSD::map_const_iterator LLSD::beginMap() const { return safe(impl).beginMap(); }
diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h
index 65ba7ddc4f..1ba57b1e95 100644
--- a/indra/llcommon/llsd.h
+++ b/indra/llcommon/llsd.h
@@ -331,6 +331,16 @@ public:
static U32 allocationCount(); ///< how many Impls have been made
static U32 outstandingCount(); ///< how many Impls are still alive
//@}
+
+private:
+ /** @name Debugging Interface */
+ //@{
+ /// Returns XML version of llsd -- only to be called from debugger
+ static const char *dumpXML(const LLSD &llsd);
+
+ /// Returns Notation version of llsd -- only to be called from debugger
+ static const char *dump(const LLSD &llsd);
+ //@}
};
struct llsd_select_bool : public std::unary_function<LLSD, LLSD::Boolean>
diff --git a/indra/llmessage/llservicebuilder.cpp b/indra/llmessage/llservicebuilder.cpp
index 4fb3530c15..22e5c4af43 100644
--- a/indra/llmessage/llservicebuilder.cpp
+++ b/indra/llmessage/llservicebuilder.cpp
@@ -116,6 +116,104 @@ std::string LLServiceBuilder::buildServiceURI(
// Find the Service Name
if(!service_url.empty() && option_map.isMap())
{
+ // throw in a ridiculously large limiter to make sure we don't
+ // loop forever with bad input.
+ int iterations = 100;
+ bool keep_looping = true;
+ while(keep_looping)
+ {
+ if(0 == --iterations)
+ {
+ keep_looping = false;
+ }
+
+ int depth = 0;
+ int deepest = 0;
+ bool find_match = false;
+ std::string::iterator iter(service_url.begin());
+ std::string::iterator end(service_url.end());
+ std::string::iterator deepest_node(service_url.end());
+ std::string::iterator deepest_node_end(service_url.end());
+ for(; iter != end; ++iter)
+ {
+ switch(*iter)
+ {
+ case '{':
+ ++depth;
+ if(depth > deepest)
+ {
+ deepest = depth;
+ deepest_node = iter;
+ find_match = true;
+ }
+ break;
+ case '}':
+ --depth;
+ if(find_match)
+ {
+ deepest_node_end = iter;
+ find_match = false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if((deepest_node == end) || (deepest_node_end == end))
+ {
+ break;
+ }
+
+ // *NOTE: since the c++ implementation only understands
+ // params and straight string substitution, so it's a
+ // known distance of 2 to skip the directive.
+ std::string key(deepest_node + 2, deepest_node_end);
+ LLSD value = option_map[key];
+ switch(*(deepest_node + 1))
+ {
+ case '$':
+ if(value.isDefined())
+ {
+ service_url.replace(
+ deepest_node,
+ deepest_node_end + 1,
+ value.asString());
+ }
+ else
+ {
+ llinfos << "Unknown key: " << key << llendl;
+ keep_looping = false;
+ }
+ break;
+ case '%':
+ {
+ std::string query_str = LLURI::mapToQueryString(value);
+ service_url.replace(
+ deepest_node,
+ deepest_node_end + 1,
+ query_str);
+ }
+ break;
+ default:
+ llinfos << "Unknown directive: " << *(deepest_node + 1)
+ << llendl;
+ keep_looping = false;
+ break;
+ }
+ }
+ }
+ if (service_url.find('{') != std::string::npos)
+ {
+ llwarns << "Constructed a likely bogus service URL: " << service_url
+ << llendl;
+ }
+ return service_url;
+}
+
+
+
+// Old, not as good implementation. Phoenix 2007-10-15
+#if 0
// Do brace replacements - NOT CURRENTLY RECURSIVE
for(LLSD::map_const_iterator option_itr = option_map.beginMap();
option_itr != option_map.endMap();
@@ -157,3 +255,4 @@ std::string LLServiceBuilder::buildServiceURI(
return service_url;
}
+#endif
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
index 857f804859..6a09a8bbec 100644
--- a/indra/llmessage/llurlrequest.cpp
+++ b/indra/llmessage/llurlrequest.cpp
@@ -480,6 +480,7 @@ bool LLURLRequest::configure()
mDetail->mHeaders);
}
curl_easy_setopt(mDetail->mCurl, CURLOPT_URL, mDetail->mURL);
+ lldebugs << "URL: " << mDetail->mURL << llendl;
curl_multi_add_handle(mDetail->mCurlMulti, mDetail->mCurl);
mDetail->mNeedToRemoveEasyHandle = true;
}
diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h
index f335316ab6..7cb6b6a618 100644
--- a/indra/llmessage/llurlrequest.h
+++ b/indra/llmessage/llurlrequest.h
@@ -42,6 +42,7 @@
#include <string>
#include "lliopipe.h"
#include "llchainio.h"
+#include "llerror.h"
class LLURLRequestDetail;
@@ -62,6 +63,7 @@ class LLURLRequestComplete;
*/
class LLURLRequest : public LLIOPipe
{
+ LOG_CLASS(LLURLRequest);
public:
/**
* @brief This enumeration is for specifying the type of request.
diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp
index d446730c33..a1b63ead75 100644
--- a/indra/llmessage/message.cpp
+++ b/indra/llmessage/message.cpp
@@ -1100,6 +1100,17 @@ void LLMessageSystem::forwardReliable(const U32 circuit_code)
sendReliable(findHost(circuit_code));
}
+S32 LLMessageSystem::forwardReliable( const LLHost &host,
+ S32 retries,
+ BOOL ping_based_timeout,
+ F32 timeout,
+ void (*callback)(void **,S32),
+ void ** callback_data)
+{
+ copyMessageRtoS();
+ return sendReliable(host, retries, ping_based_timeout, timeout, callback, callback_data);
+}
+
S32 LLMessageSystem::flushSemiReliable(const LLHost &host, void (*callback)(void **,S32), void ** callback_data)
{
F32 timeout;
diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h
index 3381ece222..4debcddf99 100644
--- a/indra/llmessage/message.h
+++ b/indra/llmessage/message.h
@@ -464,6 +464,13 @@ public:
void forwardMessage(const LLHost &host);
void forwardReliable(const LLHost &host);
void forwardReliable(const U32 circuit_code);
+ S32 forwardReliable(
+ const LLHost &host,
+ S32 retries,
+ BOOL ping_based_timeout,
+ F32 timeout,
+ void (*callback)(void **,S32),
+ void ** callback_data);
LLHTTPClient::ResponderPtr createResponder(const std::string& name);
S32 sendMessage(const LLHost &host);
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index a6a9447e39..c319ef97af 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -2117,7 +2117,6 @@ BOOL idle_startup()
msg->setHandlerFuncFast(_PREHASH_PreloadSound, process_preload_sound);
msg->setHandlerFuncFast(_PREHASH_AttachedSound, process_attached_sound);
msg->setHandlerFuncFast(_PREHASH_AttachedSoundGainChange, process_attached_sound_gain_change);
- //msg->setHandlerFuncFast(_PREHASH_AttachedSoundCutoffRadius, process_attached_sound_cutoff_radius);
llinfos << "Initialization complete" << llendl;
@@ -2771,7 +2770,6 @@ void register_viewer_callbacks(LLMessageSystem* msg)
msg->setHandlerFuncFast(_PREHASH_MeanCollisionAlert, process_mean_collision_alert_message, NULL);
msg->setHandlerFunc("ViewerFrozenMessage", process_frozen_message);
- //msg->setHandlerFuncFast(_PREHASH_RequestAvatarInfo, process_avatar_info_request);
msg->setHandlerFuncFast(_PREHASH_NameValuePair, process_name_value);
msg->setHandlerFuncFast(_PREHASH_RemoveNameValuePair, process_remove_name_value);
msg->setHandlerFuncFast(_PREHASH_AvatarAnimation, process_avatar_animation);
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 2011275bdb..7f349fe65b 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -3398,29 +3398,6 @@ void process_attached_sound_gain_change(LLMessageSystem *mesgsys, void **user_da
objectp->adjustAudioGain(gain);
}
-/* Unused July 2006
-void process_attached_sound_cutoff_radius(LLMessageSystem *mesgsys, void **user_data)
-{
- F32 radius = 0;
- LLUUID object_guid;
- LLViewerObject *objectp = NULL;
-
- mesgsys->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_guid);
-
- if (!((objectp = gObjectList.findObject(object_guid))))
- {
- // we don't know about this object, just bail
- return;
- }
-
- mesgsys->getF32Fast(_PREHASH_DataBlock, _PREHASH_Radius, radius);
-
- if (gAudiop)
- {
-// gAudiop->attachToObject(sound_guid, object_guid, gain, priority, flags);
- }
-}
-*/
void process_health_message(LLMessageSystem *mesgsys, void **user_data)
{
@@ -3566,19 +3543,6 @@ void process_sim_stats(LLMessageSystem *msg, void **user_data)
}
-// This info is requested by the simulator when the agent first logs in
-// or when it moves into a simulator in which it did not already have
-// a child agent.
-/*
-void process_avatar_info_request(LLMessageSystem *mesgsys, void **user_data)
-{
- llinfos << "process_avatar_info_request()" << llendl;
-
- // Send the avatar appearance (parameters and texture entry UUIDs)
- gAgent.sendAgentSetAppearance();
- send_agent_update(TRUE, TRUE);
-}*/
-
void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data)
{
diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h
index 2c5005c167..f7739f0871 100644
--- a/indra/newview/llviewermessage.h
+++ b/indra/newview/llviewermessage.h
@@ -87,12 +87,10 @@ void process_sound_trigger(LLMessageSystem *mesgsys, void **user_data);
void process_preload_sound( LLMessageSystem *mesgsys, void **user_data);
void process_attached_sound( LLMessageSystem *mesgsys, void **user_data);
void process_attached_sound_gain_change( LLMessageSystem *mesgsys, void **user_data);
-//void process_attached_sound_cutoff_radius( LLMessageSystem *mesgsys, void **user_data);
void process_energy_statistics(LLMessageSystem *mesgsys, void **user_data);
void process_health_message(LLMessageSystem *mesgsys, void **user_data);
void process_sim_stats(LLMessageSystem *mesgsys, void **user_data);
void process_shooter_agent_hit(LLMessageSystem* msg, void** user_data);
-void process_avatar_info_request(LLMessageSystem *mesgsys, void **user_data);
void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data);
void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data);
void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data);
diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h
index 0669f34c2f..0e0265d30b 100644
--- a/indra/newview/llviewerprecompiledheaders.h
+++ b/indra/newview/llviewerprecompiledheaders.h
@@ -180,7 +180,6 @@
#include "llinstantmessage.h"
#include "llinvite.h"
//#include "llloginflags.h"
-#include "lllogtextmessage.h"
#include "llmail.h"
#include "llmessagethrottle.h"
#include "llnamevalue.h"
diff --git a/indra/test/llservicebuilder_tut.cpp b/indra/test/llservicebuilder_tut.cpp
index 127a2a1220..14f3774f7c 100644
--- a/indra/test/llservicebuilder_tut.cpp
+++ b/indra/test/llservicebuilder_tut.cpp
@@ -113,5 +113,66 @@ namespace tut
test_url ,
"/proc/do/something/useful?estate_id=1&query=public");
}
+
+ template<> template<>
+ void ServiceBuilderTestObject::test<6>()
+ {
+ LLSD test_block;
+ test_block["service-builder"] = "Which way to the {${$baz}}?";
+ mServiceBuilder.createServiceDefinition(
+ "ServiceBuilderTest",
+ test_block["service-builder"]);
+
+ LLSD data_map;
+ data_map["foo"] = "bar";
+ data_map["baz"] = "foo";
+ std::string test_url = mServiceBuilder.buildServiceURI(
+ "ServiceBuilderTest",
+ data_map);
+ ensure_equals(
+ "recursive url creation",
+ test_url ,
+ "Which way to the bar?");
+ }
+
+ template<> template<>
+ void ServiceBuilderTestObject::test<7>()
+ {
+ LLSD test_block;
+ test_block["service-builder"] = "Which way to the {$foo}?";
+ mServiceBuilder.createServiceDefinition(
+ "ServiceBuilderTest",
+ test_block["service-builder"]);
+
+ LLSD data_map;
+ data_map["baz"] = "foo";
+ std::string test_url = mServiceBuilder.buildServiceURI(
+ "ServiceBuilderTest",
+ data_map);
+ ensure_equals(
+ "fails to do replacement",
+ test_url ,
+ "Which way to the {$foo}?");
+ }
+
+ template<> template<>
+ void ServiceBuilderTestObject::test<8>()
+ {
+ LLSD test_block;
+ test_block["service-builder"] = "/proc/{$proc}{%params}";
+ mServiceBuilder.createServiceDefinition(
+ "ServiceBuilderTest",
+ test_block["service-builder"]);
+ LLSD data_map;
+ data_map["proc"] = "do/something/useful";
+ data_map["params"] = LLSD();
+ std::string test_url = mServiceBuilder.buildServiceURI(
+ "ServiceBuilderTest",
+ data_map);
+ ensure_equals(
+ "strip params",
+ test_url ,
+ "/proc/do/something/useful");
+ }
}