summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/lib/python/indra/base/config.py4
-rw-r--r--indra/lib/python/indra/base/lluuid.py5
-rw-r--r--indra/lib/python/indra/ipc/mysql_pool.py51
-rw-r--r--indra/lib/python/indra/ipc/russ.py4
-rw-r--r--indra/lib/python/indra/ipc/servicebuilder.py8
-rw-r--r--indra/lib/python/indra/util/named_query.py191
-rw-r--r--indra/llinventory/lltransactionflags.cpp84
-rw-r--r--indra/llinventory/lltransactionflags.h18
-rw-r--r--indra/llmessage/llhttpclient.cpp4
-rw-r--r--indra/llmessage/llhttpclient.h8
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);