diff options
| -rw-r--r-- | etc/message.xml | 2 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/lluuid.py | 4 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/mysql_pool.py | 2 | ||||
| -rw-r--r-- | indra/lib/python/indra/util/named_query.py | 387 | ||||
| -rw-r--r-- | indra/llcommon/llsd.cpp | 39 | ||||
| -rw-r--r-- | indra/llcommon/llsd.h | 10 | ||||
| -rw-r--r-- | indra/llmessage/llservicebuilder.cpp | 99 | ||||
| -rw-r--r-- | indra/llmessage/llurlrequest.cpp | 1 | ||||
| -rw-r--r-- | indra/llmessage/llurlrequest.h | 2 | ||||
| -rw-r--r-- | indra/llmessage/message.cpp | 11 | ||||
| -rw-r--r-- | indra/llmessage/message.h | 7 | ||||
| -rw-r--r-- | indra/newview/llstartup.cpp | 2 | ||||
| -rw-r--r-- | indra/newview/llviewermessage.cpp | 36 | ||||
| -rw-r--r-- | indra/newview/llviewermessage.h | 2 | ||||
| -rw-r--r-- | indra/newview/llviewerprecompiledheaders.h | 1 | ||||
| -rw-r--r-- | indra/test/llservicebuilder_tut.cpp | 61 | 
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"); +	}  } | 
