diff options
Diffstat (limited to 'indra')
| -rw-r--r-- | indra/lib/python/indra/base/config.py | 4 | ||||
| -rw-r--r-- | indra/lib/python/indra/base/lluuid.py | 5 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/mysql_pool.py | 51 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/russ.py | 4 | ||||
| -rw-r--r-- | indra/lib/python/indra/ipc/servicebuilder.py | 8 | ||||
| -rw-r--r-- | indra/lib/python/indra/util/named_query.py | 191 | ||||
| -rw-r--r-- | indra/llinventory/lltransactionflags.cpp | 84 | ||||
| -rw-r--r-- | indra/llinventory/lltransactionflags.h | 18 | ||||
| -rw-r--r-- | indra/llmessage/llhttpclient.cpp | 4 | ||||
| -rw-r--r-- | indra/llmessage/llhttpclient.h | 8 | 
10 files changed, 367 insertions, 10 deletions
| diff --git a/indra/lib/python/indra/base/config.py b/indra/lib/python/indra/base/config.py index d76bd7edd3..d88801aa0d 100644 --- a/indra/lib/python/indra/base/config.py +++ b/indra/lib/python/indra/base/config.py @@ -39,8 +39,8 @@ def update(xml_file):      config_file.close()      _g_config_dict.update(overrides) -def get(key): +def get(key, default = None):      global _g_config_dict      if _g_config_dict == None:          load() -    return _g_config_dict.get(key) +    return _g_config_dict.get(key, default) diff --git a/indra/lib/python/indra/base/lluuid.py b/indra/lib/python/indra/base/lluuid.py index a86ee03e72..62ed365cab 100644 --- a/indra/lib/python/indra/base/lluuid.py +++ b/indra/lib/python/indra/base/lluuid.py @@ -89,9 +89,12 @@ class UUID(object):      def __eq__(self, other):          if isinstance(other, (str, unicode)): -            other = UUID(other) +            return other == str(self)          return self._bits == getattr(other, '_bits', '') +    def __ne__(self, other): +        return not self.__eq__(other) +      def __le__(self, other):          return self._bits <= other._bits diff --git a/indra/lib/python/indra/ipc/mysql_pool.py b/indra/lib/python/indra/ipc/mysql_pool.py index 2e0c40f850..64965c83d4 100644 --- a/indra/lib/python/indra/ipc/mysql_pool.py +++ b/indra/lib/python/indra/ipc/mysql_pool.py @@ -14,6 +14,49 @@ from indra.ipc import saranwrap  import MySQLdb +# method 2: better -- admits the existence of the pool +# dbp = my_db_connector.get() +# dbh = dbp.get() +# dbc = dbh.cursor() +# dbc.execute(named_query) +# dbc.close() +# dbp.put(dbh) + +class DatabaseConnector(object): +    """\ +@brief This is an object which will maintain a collection of database +connection pools keyed on host,databasename""" +    def __init__(self, credentials, min_size = 0, max_size = 4, *args, **kwargs): +        """\ +        @brief constructor +        @param min_size the minimum size of a child pool. +        @param max_size the maximum size of a child pool.""" +        self._min_size = min_size +        self._max_size = max_size +        self._args = args +        self._kwargs = kwargs +        self._credentials = credentials  # this is a map of hostname to username/password +        self._databases = {} + +    def credentials_for(self, host): +        if host in self._credentials: +            return self._credentials[host] +        else: +            return self._credentials.get('default', None) + +    def get(self, host, dbname): +        key = (host, dbname) +        if key not in self._databases: +            new_kwargs = self._kwargs.copy() +            new_kwargs['db'] = dbname +            new_kwargs['host'] = host +            new_kwargs.update(self.credentials_for(host)) +            dbpool = ConnectionPool(self._min_size, self._max_size, *self._args, **new_kwargs) +            self._databases[key] = dbpool + +        return self._databases[key] + +  class ConnectionPool(Pool):      """A pool which gives out saranwrapped MySQLdb connections from a pool      """ @@ -26,12 +69,14 @@ class ConnectionPool(Pool):          return saranwrap.wrap(MySQLdb).connect(*self._args, **self._kwargs)      def put(self, conn): -        # poke the process to see if it's dead or None +        # rollback any uncommitted changes, so that the next process +        # has a clean slate.  This also pokes the process to see if +        # it's dead or None          try: -            status = saranwrap.status(conn) +            conn.rollback()          except (AttributeError, DeadProcess), e:              conn = self.create() - +        # TODO figure out if we're still connected to the database          if conn:              Pool.put(self, conn)          else: diff --git a/indra/lib/python/indra/ipc/russ.py b/indra/lib/python/indra/ipc/russ.py index a9ebd8bb03..437061538b 100644 --- a/indra/lib/python/indra/ipc/russ.py +++ b/indra/lib/python/indra/ipc/russ.py @@ -61,7 +61,7 @@ def format(format_str, context):                  end = format_str.index('}', pos)                  #print "directive:", format_str[pos+1:pos+5]                  if format_str[pos + 1] == '$': -                    value = context.get(format_str[pos + 2:end]) +                    value = context[format_str[pos + 2:end]]                      if value is not None:                          value = format_value_for_path(value)                  elif format_str[pos + 1] == '%': @@ -71,7 +71,7 @@ def format(format_str, context):                      value = _fetch_url_directive(format_str[pos + 1:end])                  else:                      raise UnknownDirective, format_str[pos:end + 1] -                if not value == None: +                if value is not None:                      format_str = format_str[:pos]+str(value)+format_str[end+1:]                      substitutions += 1 diff --git a/indra/lib/python/indra/ipc/servicebuilder.py b/indra/lib/python/indra/ipc/servicebuilder.py index d422b2ed42..c2490593c3 100644 --- a/indra/lib/python/indra/ipc/servicebuilder.py +++ b/indra/lib/python/indra/ipc/servicebuilder.py @@ -17,6 +17,14 @@ try:  except:      pass +# convenience method for when you can't be arsed to make your own object (i.e. all the time) +_g_builder = None +def build(name, context): +    global _g_builder +    if _g_builder is None: +        _g_builder = ServiceBuilder() +    _g_builder.buildServiceURL(name, context) +  class ServiceBuilder(object):      def __init__(self, services_definition = services_config):          """\ diff --git a/indra/lib/python/indra/util/named_query.py b/indra/lib/python/indra/util/named_query.py new file mode 100644 index 0000000000..6e72f9be88 --- /dev/null +++ b/indra/lib/python/indra/util/named_query.py @@ -0,0 +1,191 @@ +"""\ +@file named_query.py +@author Ryan Williams, Phoenix +@date 2007-07-31 +@brief An API for running named queries. + +Copyright (c) 2007, Linden Research, Inc. +$License$ +""" + +import MySQLdb +import os +import os.path +import time + +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): +    if sql_dir is None: +        sql_dir = config.get('named-query-base-dir') +    global _g_named_manager +    _g_named_manager = NamedQueryManager( +        os.path.abspath(os.path.realpath(sql_dir))) + +def get(name): +    "@brief 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): +    # use module-global NamedQuery object to perform default substitution +    return get(name).sql(params) + +def run(connection, name, params, expect_rows = None): +    """\ +@brief given a connection, run a named query with the params + +Note that this function will fetch ALL rows. +@param connection The connection to use +@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. +@return Returns the result set as a list of dicts. +""" +    return get(name).run(connection, params, expect_rows) + +class ExpectationFailed(Exception): +    def __init__(self, message): +        self.message = message + +class NamedQuery(object): +    def __init__(self, filename): +        self._stat_interval = 5000  # 5 seconds +        self._location = filename +        self.load_contents() + +    def get_modtime(self): +        return os.path.getmtime(self._location) + +    def load_contents(self): +        self._contents = llsd.parse(open(self._location).read()) +        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() + +    def ttl(self): +        return self._ttl + +    def legacy_dbname(self): +        return self._legacy_dbname + +    def legacy_query(self): +        return self._legacy_query + +    def return_as_map(self): +        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 + +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) +        #print "SQL:", statement +        rows = cursor.execute(statement) +         +        # *NOTE: the expect_rows argument is a very cheesy way to get some +        # validation on the result set.  If you want to add more expectation +        # logic, do something more object-oriented and flexible.  Or use an ORM. +        if(self._return_as_map): +            expect_rows = 1 +        if expect_rows is not None and rows != expect_rows: +            cursor.close() +            raise ExpectationFailed("Statement expected %s rows, got %s.  Sql: %s" % ( +                expect_rows, rows, statement)) + +        # convert to dicts manually if we're not using a dictcursor +        if use_dictcursor: +            result_set = cursor.fetchall() +        else: +            if cursor.description is None: +                # an insert or something +                x = cursor.fetchall() +                cursor.close() +                return x + +            names = [x[0] for x in cursor.description] + +            result_set = [] +            for row in cursor.fetchall(): +                converted_row = {} +                for idx, col_name in enumerate(names): +                    converted_row[col_name] = row[idx] +                result_set.append(converted_row) + +        cursor.close() +        if self._return_as_map: +            return result_set[0] +        return result_set + +    def sql(self, params): +        self.refresh() + +        # build the query from the options available and the params +        base_query = [] +        base_query.append(self._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): +                    base_query.append(extra_where[params[opt]]) +                else: +                    base_query.append(extra_where) + +        full_query = '\n'.join(base_query) +         +        # do substitution +        sql = russ.format(full_query, params) +        return sql + +    def refresh(self): +        # only stat the file every so often +        now = time.time() +        if(now - self._last_check_time > self._stat_interval): +            self._last_check_time = now +            modtime = self.get_modtime() +            if(modtime > self._last_mod_time): +                self.load_contents() + +class NamedQueryManager(object): +    def __init__(self, named_queries_dir): +        self._dir = os.path.abspath(os.path.realpath(named_queries_dir)) +        self._cached_queries = {} + +    def sql(self, name, params): +        nq = self.get(name) +        return nq.sql(params) +         +    def get(self, name): +        # new up/refresh a NamedQuery based on the name +        nq = self._cached_queries.get(name) +        if nq is None: +            nq = NamedQuery(os.path.join(self._dir, name)) +            self._cached_queries[name] = nq +        return nq diff --git a/indra/llinventory/lltransactionflags.cpp b/indra/llinventory/lltransactionflags.cpp index 3f1aa14959..394dd1bd60 100644 --- a/indra/llinventory/lltransactionflags.cpp +++ b/indra/llinventory/lltransactionflags.cpp @@ -10,6 +10,8 @@  #include "linden_common.h"  #include "lltransactionflags.h" +#include "lltransactiontypes.h" +#include "lluuid.h"  const U8 TRANSACTION_FLAGS_NONE = 0;  const U8 TRANSACTION_FLAG_SOURCE_GROUP = 1; @@ -41,3 +43,85 @@ BOOL is_tf_owner_group(TransactionFlags flags)  	return ((flags & TRANSACTION_FLAG_OWNER_GROUP) == TRANSACTION_FLAG_OWNER_GROUP);  } +void append_reason( +	std::ostream& ostr, +	S32 transaction_type, +	const char* description) +{ +	switch( transaction_type ) +	{ +	case TRANS_OBJECT_SALE: +		ostr << " for " << (description ? description : "<unknown>"); +		break; +	case TRANS_LAND_SALE: +		ostr << " for a parcel of land"; +		break; +	case TRANS_LAND_PASS_SALE: +		ostr << " for a land access pass"; +		break; +	case TRANS_GROUP_LAND_DEED: +		ostr << " for deeding land"; +	default: +		break; +	} +} + +std::string build_transfer_message_to_source( +	S32 amount, +	const LLUUID& source_id, +	const LLUUID& dest_id, +	const std::string& dest_name, +	S32 transaction_type, +	const char* description) +{ +	lldebugs << "build_transfer_message_to_source: " << amount << " " +		<< source_id << " " << dest_id << " " << dest_name << " " +		<< (description?description:"(no desc)") << llendl; +	if((0 == amount) || source_id.isNull()) return ll_safe_string(description); +	std::ostringstream ostr; +	if(dest_id.isNull()) +	{ +		ostr << "You paid L$" << amount; +		switch(transaction_type) +		{ +		case TRANS_GROUP_CREATE: +			ostr << " to create a group"; +			break; +		case TRANS_GROUP_JOIN: +			ostr << " to join a group"; +			break; +		case TRANS_UPLOAD_CHARGE: +			ostr << " to upload"; +			break; +		default: +			break; +		} +	} +	else +	{ +		ostr << "You paid " << dest_name << " L$" << amount; +		append_reason(ostr, transaction_type, description); +	} +	ostr << "."; +	return ostr.str(); +} + +std::string build_transfer_message_to_destination( +	S32 amount, +	const LLUUID& dest_id, +	const LLUUID& source_id, +	const std::string& source_name, +	S32 transaction_type, +	const char* description) +{ +	lldebugs << "build_transfer_message_to_dest: " << amount << " " +		<< dest_id << " " << source_id << " " << source_name << " " +		<< (description?description:"(no desc)") << llendl; +	if(0 == amount) return std::string(); +	if(dest_id.isNull()) return ll_safe_string(description); +	std::ostringstream ostr; +	ostr << source_name << " paid you L$" << amount; +	append_reason(ostr, transaction_type, description); +	ostr << "."; +	return ostr.str(); +} diff --git a/indra/llinventory/lltransactionflags.h b/indra/llinventory/lltransactionflags.h index eaa138fef7..999ab5f671 100644 --- a/indra/llinventory/lltransactionflags.h +++ b/indra/llinventory/lltransactionflags.h @@ -24,4 +24,22 @@ BOOL is_tf_source_group(TransactionFlags flags);  BOOL is_tf_dest_group(TransactionFlags flags);  BOOL is_tf_owner_group(TransactionFlags flags); +// stupid helper functions which should be replaced with some kind of +// internationalizeable message. +std::string build_transfer_message_to_source( +	S32 amount, +	const LLUUID& source_id, +	const LLUUID& dest_id, +	const std::string& dest_name, +	S32 transaction_type, +	const char* description); + +std::string build_transfer_message_to_destination( +	S32 amount, +	const LLUUID& dest_id, +	const LLUUID& source_id, +	const std::string& source_name, +	S32 transaction_type, +	const char* description); +  #endif // LL_LLTRANSACTIONFLAGS_H diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 1763acaf8c..7158fb733d 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -54,7 +54,7 @@ void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason  	LLBufferStream istr(channels, buffer.get());  	LLSD content; -	if (200 <= status && status < 300) +	if (isGoodStatus(status))  	{  		LLSDSerialize::fromXML(content, istr);  /* @@ -73,7 +73,7 @@ void LLHTTPClient::Responder::completedRaw(U32 status, const std::string& reason  // virtual  void LLHTTPClient::Responder::completed(U32 status, const std::string& reason, const LLSD& content)  { -	if (200 <= status  &&  status < 300) +	if(isGoodStatus(status))  	{  		result(content);  	} diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index e3074ee707..f3b2360058 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -37,6 +37,14 @@ public:  		Responder();  		virtual ~Responder(); +		/** +		 * @brief return true if the status code indicates success. +		 */ +		static bool isGoodStatus(U32 status) +		{ +			return((200 <= status) && (status < 300)); +		} +  		virtual void error(U32 status, const std::string& reason);	// called with bad status codes  		virtual void result(const LLSD& content); | 
