diff options
-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); |