From a4000c3744e42fcbb638e742f3b63fa31a0dee15 Mon Sep 17 00:00:00 2001 From: Steven Bennetts Date: Fri, 8 May 2009 07:43:08 +0000 Subject: merge trunk@116587 skinning-7@119389 -> viewer-2.0.0-skinning-7 --- indra/llmessage/llares.h | 3 +- indra/llmessage/llcachename.cpp | 213 ++++++++++----------- indra/llmessage/llcachename.h | 27 ++- indra/llmessage/llhttpclientadapter.cpp | 7 +- indra/llmessage/llhttpclientadapter.h | 9 +- indra/llmessage/llhttpclientinterface.h | 7 +- indra/llmessage/llhttpnode.h | 3 +- indra/llmessage/llhttpnodeadapter.h | 15 +- indra/llmessage/llinstantmessage.cpp | 2 +- indra/llmessage/llinstantmessage.h | 3 +- indra/llmessage/llmessagesenderinterface.h | 7 +- indra/llmessage/llregionpresenceverifier.cpp | 25 ++- indra/llmessage/llregionpresenceverifier.h | 25 ++- indra/llmessage/llstoredmessage.cpp | 2 +- indra/llmessage/llstoredmessage.h | 2 +- indra/llmessage/lltemplatemessagedispatcher.cpp | 17 +- indra/llmessage/lltemplatemessagedispatcher.h | 17 +- indra/llmessage/lltransfersourceasset.cpp | 2 + indra/llmessage/lltrustedmessageservice.cpp | 17 +- indra/llmessage/lltrustedmessageservice.h | 17 +- indra/llmessage/message.h | 2 +- indra/llmessage/message_prehash.cpp | 2 +- indra/llmessage/tests/commtest.h | 23 +++ indra/llmessage/tests/llcurl_stub.cpp | 10 +- indra/llmessage/tests/llhttpclientadapter_test.cpp | 7 +- .../tests/lltemplatemessagedispatcher_test.cpp | 17 +- indra/llmessage/tests/lltesthttpclientadapter.cpp | 2 +- indra/llmessage/tests/lltesthttpclientadapter.h | 2 +- indra/llmessage/tests/lltestmessagesender.cpp | 2 +- indra/llmessage/tests/lltestmessagesender.h | 2 +- .../tests/lltrustedmessageservice_test.cpp | 17 +- indra/llmessage/tests/networkio.h | 23 +++ 32 files changed, 306 insertions(+), 223 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llmessage/llares.h b/indra/llmessage/llares.h index 96d7f6dd31..c709a08499 100644 --- a/indra/llmessage/llares.h +++ b/indra/llmessage/llares.h @@ -45,7 +45,8 @@ # include #endif -#include "llmemory.h" +#include "llpointer.h" +#include "llrefcount.h" #include "lluri.h" class LLQueryResponder; diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index 799bc83e20..629bd3836d 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -43,11 +43,6 @@ #include "lluuid.h" #include "message.h" -// Constants -static const std::string CN_WAITING("(Loading...)"); // *TODO: translate -static const std::string CN_NOBODY("(nobody)"); // *TODO: translate -static const std::string CN_NONE("(none)"); // *TODO: translate - // llsd serialization constants static const std::string AGENTS("agents"); static const std::string GROUPS("groups"); @@ -65,6 +60,7 @@ const S32 CN_FILE_VERSION = 2; // Globals LLCacheName* gCacheName = NULL; +std::map LLCacheName::sCacheName; /// --------------------------------------------------------------------------- /// class LLCacheNameEntry @@ -92,17 +88,19 @@ class PendingReply { public: LLUUID mID; - LLCacheNameCallback mCallback; + LLCacheNameSignal mSignal; LLHost mHost; - void* mData; - PendingReply(const LLUUID& id, LLCacheNameCallback callback, void* data = NULL) - : mID(id), mCallback(callback), mData(data) - { } - + PendingReply(const LLUUID& id, const LLHost& host) - : mID(id), mCallback(0), mHost(host) - { } - + : mID(id), mHost(host) + { + } + + boost::signals::connection setCallback(const LLCacheNameCallback& cb) + { + return mSignal.connect(cb); + } + void done() { mID.setNull(); } bool isDone() const { return mID.isNull() != FALSE; } }; @@ -187,10 +185,9 @@ void ReplySender::flush() typedef std::set AskQueue; -typedef std::vector ReplyQueue; +typedef std::list ReplyQueue; typedef std::map PendingQueue; typedef std::map Cache; -typedef std::vector Observers; class LLCacheName::Impl { @@ -211,13 +208,16 @@ public: ReplyQueue mReplyQueue; // requests awaiting replies from us - Observers mObservers; + LLCacheNameSignal mSignal; LLFrameTimer mProcessTimer; Impl(LLMessageSystem* msg); ~Impl(); - + + boost::signals::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback); + void addPending(const LLUUID& id, const LLHost& host); + void processPendingAsks(); void processPendingReplies(); void sendRequest(const char* msg_name, const AskQueue& queue); @@ -231,8 +231,6 @@ public: static void handleUUIDNameReply(LLMessageSystem* msg, void** userdata); static void handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userdata); static void handleUUIDGroupNameReply(LLMessageSystem* msg, void** userdata); - - void notifyObservers(const LLUUID& id, const std::string& first, const std::string& last, BOOL group); }; @@ -247,6 +245,9 @@ LLCacheName::LLCacheName(LLMessageSystem* msg) LLCacheName::LLCacheName(LLMessageSystem* msg, const LLHost& upstream_host) : impl(* new Impl(msg)) { + sCacheName["waiting"] = "(Loading...)"; + sCacheName["nobody"] = "(nobody)"; + sCacheName["none"] = "(none)"; setUpstream(upstream_host); } @@ -272,52 +273,31 @@ LLCacheName::Impl::Impl(LLMessageSystem* msg) LLCacheName::Impl::~Impl() { for_each(mCache.begin(), mCache.end(), DeletePairedPointer()); + for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer()); } - -void LLCacheName::setUpstream(const LLHost& upstream_host) +boost::signals::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback) { - impl.mUpstreamHost = upstream_host; + PendingReply* reply = new PendingReply(id, LLHost()); + boost::signals::connection res = reply->setCallback(callback); + mReplyQueue.push_back(reply); + return res; } -void LLCacheName::addObserver(LLCacheNameCallback callback) +void LLCacheName::Impl::addPending(const LLUUID& id, const LLHost& host) { - impl.mObservers.push_back(callback); + PendingReply* reply = new PendingReply(id, host); + mReplyQueue.push_back(reply); } -void LLCacheName::removeObserver(LLCacheNameCallback callback) +void LLCacheName::setUpstream(const LLHost& upstream_host) { - Observers::iterator it = impl.mObservers.begin(); - Observers::iterator end = impl.mObservers.end(); - - for ( ; it != end; ++it) - { - const LLCacheNameCallback& cb = (*it); - if (cb == callback) - { - impl.mObservers.erase(it); - return; - } - } + impl.mUpstreamHost = upstream_host; } -void LLCacheName::cancelCallback(const LLUUID& id, LLCacheNameCallback callback, void* user_data) +boost::signals::connection LLCacheName::addObserver(const LLCacheNameCallback& callback) { - ReplyQueue::iterator it = impl.mReplyQueue.begin(); - ReplyQueue::iterator end = impl.mReplyQueue.end(); - - for(; it != end; ++it) - { - const PendingReply& reply = (*it); - - if ((callback == reply.mCallback) - && (id == reply.mID) - && (user_data == reply.mData) ) - { - impl.mReplyQueue.erase(it); - return; - } - } + return impl.mSignal.connect(callback); } void LLCacheName::importFile(LLFILE* fp) @@ -493,7 +473,7 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las { if(id.isNull()) { - first = CN_NOBODY; + first = sCacheName["nobody"]; last.clear(); return FALSE; } @@ -507,7 +487,7 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las } else { - first = CN_WAITING; + first = sCacheName["waiting"]; last.clear(); if (!impl.isRequestPending(id)) { @@ -517,6 +497,14 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las } } +// static +void LLCacheName::LocalizeCacheName(std::string key, std::string value) +{ + if (key!="" && value!= "" ) + sCacheName[key]=value; + else + llwarns<< " Error localizing cache key " << key << " To "<< value<mIsGroup) { - callback(id, entry->mGroupName, "", entry->mIsGroup, user_data); + signal(id, entry->mGroupName, "", entry->mIsGroup); } else { - callback(id, entry->mFirstName, entry->mLastName, entry->mIsGroup, user_data); + signal(id, entry->mFirstName, entry->mLastName, entry->mIsGroup); } } else @@ -597,8 +595,14 @@ void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callb impl.mAskNameQueue.insert(id); } } - impl.mReplyQueue.push_back(PendingReply(id, callback, user_data)); + res = impl.addPending(id, callback); } + return res; +} + +boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) +{ + return get(id, is_group, boost::bind(callback, _1, _2, _3, _4, user_data)); } void LLCacheName::processPending() @@ -684,14 +688,14 @@ void LLCacheName::dumpStats() << " AskGroup=" << impl.mAskGroupQueue.size() << " Pending=" << impl.mPendingQueue.size() << " Reply=" << impl.mReplyQueue.size() - << " Observers=" << impl.mObservers.size() +// << " Observers=" << impl.mSignal.size() << llendl; } //static std::string LLCacheName::getDefaultName() { - return CN_WAITING; + return sCacheName["waiting"]; } void LLCacheName::Impl::processPendingAsks() @@ -704,50 +708,49 @@ void LLCacheName::Impl::processPendingAsks() void LLCacheName::Impl::processPendingReplies() { - ReplyQueue::iterator it = mReplyQueue.begin(); - ReplyQueue::iterator end = mReplyQueue.end(); - // First call all the callbacks, because they might send messages. - for(; it != end; ++it) + for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it) { - LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID); + PendingReply* reply = *it; + LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID); if(!entry) continue; - if (it->mCallback) + if (!entry->mIsGroup) { - if (!entry->mIsGroup) - { - (it->mCallback)(it->mID, - entry->mFirstName, entry->mLastName, - FALSE, it->mData); - } - else { - (it->mCallback)(it->mID, - entry->mGroupName, "", - TRUE, it->mData); - } + (reply->mSignal)(reply->mID, entry->mFirstName, entry->mLastName, FALSE); + } + else + { + (reply->mSignal)(reply->mID, entry->mGroupName, "", TRUE); } } // Forward on all replies, if needed. ReplySender sender(mMsg); - for (it = mReplyQueue.begin(); it != end; ++it) + for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it) { - LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID); + PendingReply* reply = *it; + LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID); if(!entry) continue; - if (it->mHost.isOk()) + if (reply->mHost.isOk()) { - sender.send(it->mID, *entry, it->mHost); + sender.send(reply->mID, *entry, reply->mHost); } - it->done(); + reply->done(); + } + + for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ) + { + ReplyQueue::iterator curit = it++; + PendingReply* reply = *curit; + if (reply->isDone()) + { + delete reply; + mReplyQueue.erase(curit); + } } - - mReplyQueue.erase( - remove_if(mReplyQueue.begin(), mReplyQueue.end(), - std::mem_fun_ref(&PendingReply::isDone)), - mReplyQueue.end()); } @@ -785,18 +788,6 @@ void LLCacheName::Impl::sendRequest( } } -void LLCacheName::Impl::notifyObservers(const LLUUID& id, - const std::string& first, const std::string& last, BOOL is_group) -{ - for (Observers::const_iterator i = mObservers.begin(), - end = mObservers.end(); - i != end; - ++i) - { - (**i)(id, first, last, is_group, NULL); - } -} - bool LLCacheName::Impl::isRequestPending(const LLUUID& id) { U32 now = (U32)time(NULL); @@ -863,7 +854,7 @@ void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup) } } - mReplyQueue.push_back(PendingReply(id, fromHost)); + addPending(id, fromHost); } } } @@ -901,11 +892,11 @@ void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup) if (!isGroup) { - notifyObservers(id, entry->mFirstName, entry->mLastName, FALSE); + mSignal(id, entry->mFirstName, entry->mLastName, FALSE); } else { - notifyObservers(id, entry->mGroupName, "", TRUE); + mSignal(id, entry->mGroupName, "", TRUE); } } } diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h index 2757b86a7c..414b6590f6 100644 --- a/indra/llmessage/llcachename.h +++ b/indra/llmessage/llcachename.h @@ -33,12 +33,22 @@ #ifndef LL_LLCACHENAME_H #define LL_LLCACHENAME_H +#include +#include + class LLMessageSystem; class LLHost; class LLUUID; -// agent_id/group_id, first_name, last_name, is_group, user_data -typedef void (*LLCacheNameCallback)(const LLUUID&, const std::string&, const std::string&, BOOL, void*); + +typedef boost::signal LLCacheNameSignal; +typedef LLCacheNameSignal::slot_type LLCacheNameCallback; + +// Old callback with user data for compatability +typedef void (*old_callback_t)(const LLUUID&, const std::string&, const std::string&, BOOL, void*); // Here's the theory: // If you request a name that isn't in the cache, it returns "waiting" @@ -59,10 +69,7 @@ public: // for simulators, this is the data server void setUpstream(const LLHost& upstream_host); - void addObserver(LLCacheNameCallback callback); - void removeObserver(LLCacheNameCallback callback); - - void cancelCallback(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL); + boost::signals::connection addObserver(const LLCacheNameCallback& callback); // janky old format. Remove after a while. Phoenix. 2008-01-30 void importFile(LLFILE* fp); @@ -89,11 +96,10 @@ public: // If the data is currently available, may call the callback immediatly // otherwise, will request the data, and will call the callback when // available. There is no garuntee the callback will ever be called. - void get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data = NULL); + boost::signals::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); // LEGACY - void getName(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL) - { get(id, FALSE, callback, user_data); } + boost::signals::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); // This method needs to be called from time to time to send out // requests. @@ -107,7 +113,8 @@ public: void dumpStats(); // Dumps the sizes of the cache and associated queues. static std::string getDefaultName(); - + static void LocalizeCacheName(std::string key, std::string value); + static std::map sCacheName; private: class Impl; diff --git a/indra/llmessage/llhttpclientadapter.cpp b/indra/llmessage/llhttpclientadapter.cpp index bbb56960df..b6988224ce 100644 --- a/indra/llmessage/llhttpclientadapter.cpp +++ b/indra/llmessage/llhttpclientadapter.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2009&license=viewergpl$ * - * Copyright (c) 2001-2009, Linden Research, Inc. + * Copyright (c) 2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +12,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, diff --git a/indra/llmessage/llhttpclientadapter.h b/indra/llmessage/llhttpclientadapter.h index d5f3aeaf2c..7f76390d0c 100644 --- a/indra/llmessage/llhttpclientadapter.h +++ b/indra/llmessage/llhttpclientadapter.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2001-2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +12,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -33,7 +34,7 @@ #define LL_HTTPCLIENTADAPTER_H #include "llhttpclientinterface.h" -#include "llmemory.h" // LLSingleton<> +#include "llsingleton.h" // LLSingleton<> class LLHTTPClientAdapter : public LLHTTPClientInterface, public LLSingleton { diff --git a/indra/llmessage/llhttpclientinterface.h b/indra/llmessage/llhttpclientinterface.h index 1f13d46447..42a8e5cd0a 100644 --- a/indra/llmessage/llhttpclientinterface.h +++ b/indra/llmessage/llhttpclientinterface.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2001-2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +12,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, diff --git a/indra/llmessage/llhttpnode.h b/indra/llmessage/llhttpnode.h index 17ffd66e8f..3f4da69a1d 100644 --- a/indra/llmessage/llhttpnode.h +++ b/indra/llmessage/llhttpnode.h @@ -33,7 +33,8 @@ #ifndef LL_LLHTTPNODE_H #define LL_LLHTTPNODE_H -#include "llmemory.h" +#include "llpointer.h" +#include "llrefcount.h" #include "llsd.h" class LLChainIOFactory; diff --git a/indra/llmessage/llhttpnodeadapter.h b/indra/llmessage/llhttpnodeadapter.h index 08b5664162..7c3e9d81d1 100644 --- a/indra/llmessage/llhttpnodeadapter.h +++ b/indra/llmessage/llhttpnodeadapter.h @@ -3,26 +3,27 @@ * @brief Declaration of llhttpnode adapter classes * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * + * * Copyright (c) 2009, Linden Research, Inc. - * + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/llinstantmessage.cpp b/indra/llmessage/llinstantmessage.cpp index 44be3ae388..aa64232e42 100644 --- a/indra/llmessage/llinstantmessage.cpp +++ b/indra/llmessage/llinstantmessage.cpp @@ -41,7 +41,7 @@ #include "llsd.h" #include "llsdserialize.h" #include "llsdutil.h" -#include "llmemory.h" +#include "llpointer.h" #include "message.h" #include "message.h" diff --git a/indra/llmessage/llinstantmessage.h b/indra/llmessage/llinstantmessage.h index 9ce6a10c80..272e753f3c 100644 --- a/indra/llmessage/llinstantmessage.h +++ b/indra/llmessage/llinstantmessage.h @@ -36,7 +36,8 @@ #include "llhost.h" #include "lluuid.h" #include "llsd.h" -#include "llmemory.h" +#include "llrefcount.h" +#include "llpointer.h" #include "v3math.h" class LLMessageSystem; diff --git a/indra/llmessage/llmessagesenderinterface.h b/indra/llmessage/llmessagesenderinterface.h index 4082666339..119eb1d7f7 100644 --- a/indra/llmessage/llmessagesenderinterface.h +++ b/indra/llmessage/llmessagesenderinterface.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2001-2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +12,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, diff --git a/indra/llmessage/llregionpresenceverifier.cpp b/indra/llmessage/llregionpresenceverifier.cpp index 552cf4cbdb..0527d5cb8d 100644 --- a/indra/llmessage/llregionpresenceverifier.cpp +++ b/indra/llmessage/llregionpresenceverifier.cpp @@ -4,14 +4,25 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * - * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of - * this source code is governed by the Linden Lab Source Code Disclosure - * Agreement ("Agreement") previously entered between you and Linden - * Lab. By accessing, using, copying, modifying or distributing this - * software, you acknowledge that you have been informed of your - * obligations under the Agreement and agree to abide by those obligations. + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, diff --git a/indra/llmessage/llregionpresenceverifier.h b/indra/llmessage/llregionpresenceverifier.h index d1de608ec6..10602450d8 100644 --- a/indra/llmessage/llregionpresenceverifier.h +++ b/indra/llmessage/llregionpresenceverifier.h @@ -4,14 +4,25 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * - * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of - * this source code is governed by the Linden Lab Source Code Disclosure - * Agreement ("Agreement") previously entered between you and Linden - * Lab. By accessing, using, copying, modifying or distributing this - * software, you acknowledge that you have been informed of your - * obligations under the Agreement and agree to abide by those obligations. + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, diff --git a/indra/llmessage/llstoredmessage.cpp b/indra/llmessage/llstoredmessage.cpp index 615eff405d..32cbb15cb3 100644 --- a/indra/llmessage/llstoredmessage.cpp +++ b/indra/llmessage/llstoredmessage.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2009&license=viewergpl$ * - * Copyright (c) 2001-2009, Linden Research, Inc. + * Copyright (c) 2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab diff --git a/indra/llmessage/llstoredmessage.h b/indra/llmessage/llstoredmessage.h index e817f19bd2..5069c2cb2e 100644 --- a/indra/llmessage/llstoredmessage.h +++ b/indra/llmessage/llstoredmessage.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2009&license=viewergpl$ * - * Copyright (c) 2001-2009, Linden Research, Inc. + * Copyright (c) 2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab diff --git a/indra/llmessage/lltemplatemessagedispatcher.cpp b/indra/llmessage/lltemplatemessagedispatcher.cpp index 3bbf3a058d..ab1beb362b 100644 --- a/indra/llmessage/lltemplatemessagedispatcher.cpp +++ b/indra/llmessage/lltemplatemessagedispatcher.cpp @@ -3,26 +3,27 @@ * @brief LLTemplateMessageDispatcher class * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/lltemplatemessagedispatcher.h b/indra/llmessage/lltemplatemessagedispatcher.h index b1e74f47bb..fa861e4e42 100644 --- a/indra/llmessage/lltemplatemessagedispatcher.h +++ b/indra/llmessage/lltemplatemessagedispatcher.h @@ -3,26 +3,27 @@ * @brief LLTemplateMessageDispatcher class * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index a4d59275b9..059c1bdfa1 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -270,6 +270,7 @@ bool is_asset_fetch_by_id_allowed(LLAssetType::EType type) case LLAssetType::AT_BODYPART: case LLAssetType::AT_ANIMATION: case LLAssetType::AT_GESTURE: + case LLAssetType::AT_FAVORITE: rv = true; break; default: @@ -292,6 +293,7 @@ bool is_asset_id_knowable(LLAssetType::EType type) case LLAssetType::AT_BODYPART: case LLAssetType::AT_ANIMATION: case LLAssetType::AT_GESTURE: + case LLAssetType::AT_FAVORITE: rv = true; break; default: diff --git a/indra/llmessage/lltrustedmessageservice.cpp b/indra/llmessage/lltrustedmessageservice.cpp index c1a6c437a7..505ece57b0 100644 --- a/indra/llmessage/lltrustedmessageservice.cpp +++ b/indra/llmessage/lltrustedmessageservice.cpp @@ -3,26 +3,27 @@ * @brief LLTrustedMessageService implementation * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/lltrustedmessageservice.h b/indra/llmessage/lltrustedmessageservice.h index bc824565f1..dc37702471 100644 --- a/indra/llmessage/lltrustedmessageservice.h +++ b/indra/llmessage/lltrustedmessageservice.h @@ -3,26 +3,27 @@ * @brief LLTrustedMessageService class * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index b25b27eb0f..3d7d8984ad 100644 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -59,10 +59,10 @@ #include "llhttpclient.h" #include "llhttpnode.h" #include "llpacketack.h" +#include "llsingleton.h" #include "message_prehash.h" #include "llstl.h" #include "llmsgvariabletype.h" -#include "llmsgvariabletype.h" #include "llmessagesenderinterface.h" #include "llstoredmessage.h" diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index 43b5f8e224..4e657067cd 100644 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -405,7 +405,7 @@ char* _PREHASH_GlobalX = LLMessageStringTable::getInstance()->getString("GlobalX char* _PREHASH_GlobalY = LLMessageStringTable::getInstance()->getString("GlobalY"); char* _PREHASH_CopyRotates = LLMessageStringTable::getInstance()->getString("CopyRotates"); char* _PREHASH_KickUserAck = LLMessageStringTable::getInstance()->getString("KickUserAck"); -char* _PREHASH_TopPick = LLMessageStringTable::getInstance()->getString("TopPick"); +char* _PREHASH_TopPick = LLMessageStringTable::getInstance()->getString("TopPick"); //legacy var need to be deleted -angela char* _PREHASH_SessionID = LLMessageStringTable::getInstance()->getString("SessionID"); char* _PREHASH_GlobalZ = LLMessageStringTable::getInstance()->getString("GlobalZ"); char* _PREHASH_DeclineFriendship = LLMessageStringTable::getInstance()->getString("DeclineFriendship"); diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h index 7360230451..cf1461ed2b 100644 --- a/indra/llmessage/tests/commtest.h +++ b/indra/llmessage/tests/commtest.h @@ -5,7 +5,30 @@ * @brief * * $LicenseInfo:firstyear=2009&license=viewergpl$ + * * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ diff --git a/indra/llmessage/tests/llcurl_stub.cpp b/indra/llmessage/tests/llcurl_stub.cpp index 9d1d3fa221..afd38b0655 100644 --- a/indra/llmessage/tests/llcurl_stub.cpp +++ b/indra/llmessage/tests/llcurl_stub.cpp @@ -3,16 +3,16 @@ * @brief stub class to allow unit testing * * $LicenseInfo:firstyear=2008&license=internal$ - * - * Copyright (c) 2008, Linden Research, Inc. - * + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of * this source code is governed by the Linden Lab Source Code Disclosure - * Agreement ("Agreement") { } + * Agreement ("Agreement") previously entered between you and Linden * Lab. By accessing, using, copying, modifying or distributing this * software, you acknowledge that you have been informed of your * obligations under the Agreement and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/tests/llhttpclientadapter_test.cpp b/indra/llmessage/tests/llhttpclientadapter_test.cpp index bde76db08b..250fa100b6 100644 --- a/indra/llmessage/tests/llhttpclientadapter_test.cpp +++ b/indra/llmessage/tests/llhttpclientadapter_test.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2001-2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +12,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, diff --git a/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp b/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp index a6f5659352..d57f17f270 100644 --- a/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp +++ b/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp @@ -3,26 +3,27 @@ * @brief LLTrustedMessageService unit tests * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/tests/lltesthttpclientadapter.cpp b/indra/llmessage/tests/lltesthttpclientadapter.cpp index 1140458918..9bb597feec 100644 --- a/indra/llmessage/tests/lltesthttpclientadapter.cpp +++ b/indra/llmessage/tests/lltesthttpclientadapter.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=internal$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of * this source code is governed by the Linden Lab Source Code Disclosure diff --git a/indra/llmessage/tests/lltesthttpclientadapter.h b/indra/llmessage/tests/lltesthttpclientadapter.h index d032503685..8853fc44ec 100644 --- a/indra/llmessage/tests/lltesthttpclientadapter.h +++ b/indra/llmessage/tests/lltesthttpclientadapter.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=internal$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of * this source code is governed by the Linden Lab Source Code Disclosure diff --git a/indra/llmessage/tests/lltestmessagesender.cpp b/indra/llmessage/tests/lltestmessagesender.cpp index a37aa4c566..2c8456e0a8 100644 --- a/indra/llmessage/tests/lltestmessagesender.cpp +++ b/indra/llmessage/tests/lltestmessagesender.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=internal$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of * this source code is governed by the Linden Lab Source Code Disclosure diff --git a/indra/llmessage/tests/lltestmessagesender.h b/indra/llmessage/tests/lltestmessagesender.h index 83c0eff4d3..dfbef6be64 100644 --- a/indra/llmessage/tests/lltestmessagesender.h +++ b/indra/llmessage/tests/lltestmessagesender.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=internal$ * - * Copyright (c) 2008, Linden Research, Inc. + * Copyright (c) 2008-2009, Linden Research, Inc. * * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of * this source code is governed by the Linden Lab Source Code Disclosure diff --git a/indra/llmessage/tests/lltrustedmessageservice_test.cpp b/indra/llmessage/tests/lltrustedmessageservice_test.cpp index 44595391df..0a3da4b467 100644 --- a/indra/llmessage/tests/lltrustedmessageservice_test.cpp +++ b/indra/llmessage/tests/lltrustedmessageservice_test.cpp @@ -3,26 +3,27 @@ * @brief LLTrustedMessageService unit tests * * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * + * Copyright (c) 2009, Linden Research, Inc. + * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 - * + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception - * + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. diff --git a/indra/llmessage/tests/networkio.h b/indra/llmessage/tests/networkio.h index 11c5cc07fc..0ebe369ea2 100644 --- a/indra/llmessage/tests/networkio.h +++ b/indra/llmessage/tests/networkio.h @@ -5,7 +5,30 @@ * @brief * * $LicenseInfo:firstyear=2009&license=viewergpl$ + * * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ -- cgit v1.3 From 3800c0df910c83e987184d541b868168fc2b5bec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 May 2009 21:08:08 +0000 Subject: svn merge -r114679:114681 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-7 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-8 --- indra/llcommon/CMakeLists.txt | 9 + indra/llcommon/lldependencies.cpp | 86 +++ indra/llcommon/lldependencies.h | 779 ++++++++++++++++++++ indra/llcommon/llerror.cpp | 10 +- indra/llcommon/llerrorcontrol.h | 32 +- indra/llcommon/llevent.cpp | 2 + indra/llcommon/llevent.h | 5 + indra/llcommon/llevents.cpp | 501 +++++++++++++ indra/llcommon/llevents.h | 822 ++++++++++++++++++++++ indra/llcommon/lllazy.cpp | 23 + indra/llcommon/lllazy.h | 382 ++++++++++ indra/llcommon/stringize.h | 75 ++ indra/llcommon/tests/lllazy_test.cpp | 227 ++++++ indra/llmessage/CMakeLists.txt | 6 +- indra/llmessage/llcachename.cpp | 16 +- indra/llmessage/llcachename.h | 16 +- indra/llmessage/llsdmessage.cpp | 150 ++++ indra/llmessage/llsdmessage.h | 146 ++++ indra/llmessage/tests/llsdmessage_test.cpp | 113 +++ indra/llui/llbutton.cpp | 16 +- indra/llui/llbutton.h | 16 +- indra/llui/llmenugl.cpp | 2 + indra/llui/llmenugl.h | 8 +- indra/llui/llmultislider.h | 4 +- indra/llui/llmultisliderctrl.cpp | 4 +- indra/llui/llmultisliderctrl.h | 4 +- indra/llui/llnotifications.cpp | 23 +- indra/llui/llnotifications.h | 87 +-- indra/llui/llslider.h | 4 +- indra/llui/llsliderctrl.cpp | 4 +- indra/llui/llsliderctrl.h | 4 +- indra/llui/llui.h | 4 +- indra/llui/lluictrl.cpp | 4 +- indra/llui/lluictrl.h | 18 +- indra/llui/llview.cpp | 2 + indra/llxml/CMakeLists.txt | 1 - indra/llxml/llcontrol.h | 8 +- indra/newview/CMakeLists.txt | 5 + indra/newview/llagent.cpp | 209 +++--- indra/newview/llagent.h | 4 +- indra/newview/llagentlanguage.h | 2 +- indra/newview/llappviewer.cpp | 13 +- indra/newview/llcapabilitylistener.cpp | 183 +++++ indra/newview/llcapabilitylistener.h | 113 +++ indra/newview/llcapabilityprovider.h | 39 + indra/newview/llfloatergroups.cpp | 2 + indra/newview/llfloatergroups.h | 8 +- indra/newview/llfolderview.h | 2 +- indra/newview/llinventorybridge.cpp | 2 + indra/newview/llnetmap.cpp | 2 + indra/newview/llteleporthistory.h | 4 +- indra/newview/lltoolpipette.h | 4 +- indra/newview/llviewerinventory.cpp | 63 +- indra/newview/llviewermedia.cpp | 2 +- indra/newview/llviewermenu.cpp | 3 +- indra/newview/llviewermenufile.cpp | 2 + indra/newview/llviewerparcelmgr.cpp | 4 +- indra/newview/llviewerparcelmgr.h | 12 +- indra/newview/llviewerregion.cpp | 33 +- indra/newview/llviewerregion.h | 26 +- indra/newview/llviewerthrottle.cpp | 2 + indra/newview/tests/llcapabilitylistener_test.cpp | 274 ++++++++ indra/test/CMakeLists.txt | 4 + indra/test/llevents_tut.cpp | 793 +++++++++++++++++++++ install.xml | 16 +- 65 files changed, 5130 insertions(+), 309 deletions(-) create mode 100644 indra/llcommon/lldependencies.cpp create mode 100644 indra/llcommon/lldependencies.h create mode 100644 indra/llcommon/llevents.cpp create mode 100644 indra/llcommon/llevents.h create mode 100644 indra/llcommon/lllazy.cpp create mode 100644 indra/llcommon/lllazy.h create mode 100644 indra/llcommon/stringize.h create mode 100644 indra/llcommon/tests/lllazy_test.cpp create mode 100644 indra/llmessage/llsdmessage.cpp create mode 100644 indra/llmessage/llsdmessage.h create mode 100644 indra/llmessage/tests/llsdmessage_test.cpp create mode 100644 indra/newview/llcapabilitylistener.cpp create mode 100644 indra/newview/llcapabilitylistener.h create mode 100644 indra/newview/llcapabilityprovider.h create mode 100644 indra/newview/tests/llcapabilitylistener_test.cpp create mode 100644 indra/test/llevents_tut.cpp (limited to 'indra/llmessage') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index c4663cc145..694f3d5de8 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -3,6 +3,7 @@ project(llcommon) include(00-Common) +include(LLAddBuildTest) include(LLCommon) include(Boost) @@ -28,9 +29,11 @@ set(llcommon_SOURCE_FILES llcriticaldamp.cpp llcursortypes.cpp lldate.cpp + lldependencies.cpp llerror.cpp llerrorthread.cpp llevent.cpp + llevents.cpp llfasttimer.cpp llfile.cpp llfindlocale.cpp @@ -103,6 +106,7 @@ set(llcommon_HEADER_FILES lldarrayptr.h lldate.h lldefs.h + lldependencies.h lldepthstack.h lldlinked.h lldoubledispatch.h @@ -114,6 +118,7 @@ set(llcommon_HEADER_FILES llerrorlegacy.h llerrorthread.h llevent.h + llevents.h lleventemitter.h llextendedstatus.h llfasttimer.h @@ -129,6 +134,7 @@ set(llcommon_HEADER_FILES llindraconfigfile.h llinstancetracker.h llkeythrottle.h + lllazy.h lllinkedqueue.h llliveappconfig.h lllivefile.h @@ -194,6 +200,7 @@ set(llcommon_HEADER_FILES stdenums.h stdtypes.h string_table.h + stringize.h timer.h timing.h u64.h @@ -214,3 +221,5 @@ target_link_libraries( ${BOOST_PROGRAM_OPTIONS_LIBRARY} ${BOOST_REGEX_LIBRARY} ) + +ADD_BUILD_TEST(lllazy llcommon) diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp new file mode 100644 index 0000000000..ffb5cfbdaa --- /dev/null +++ b/indra/llcommon/lldependencies.cpp @@ -0,0 +1,86 @@ +/** + * @file lldependencies.cpp + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief Implementation for lldependencies. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lldependencies.h" +// STL headers +#include +#include +// std headers +// external library headers +#include // for boost::graph_traits +#include +#include +#include +// other Linden headers + +LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const +{ + // Construct a Boost Graph Library graph according to the constraints + // we've collected. It seems as though we ought to be able to capture + // the uniqueness of vertex keys using a setS of vertices with a + // string property -- but I don't yet understand adjacency_list well + // enough to get there. All the examples I've seen so far use integers + // for vertices. + // Define the Graph type. Use a vector for vertices so we can use the + // default topological_sort vertex lookup by int index. Use a set for + // edges because the same dependency may be stated twice: Node "a" may + // specify that it must precede "b", while "b" may also state that it + // must follow "a". + typedef boost::adjacency_list Graph; + // Instantiate the graph. Without vertex properties, we need say no + // more about vertices than the total number. + Graph g(edges.begin(), edges.end(), vertices); + // topo sort + typedef boost::graph_traits::vertex_descriptor VertexDesc; + typedef std::vector SortedList; + SortedList sorted; + // note that it throws not_a_dag if it finds a cycle + try + { + boost::topological_sort(g, std::back_inserter(sorted)); + } + catch (const boost::not_a_dag& e) + { + // translate to the exception we define + std::ostringstream out; + out << "LLDependencies cycle: " << e.what() << '\n'; + // Omit independent nodes: display only those that might contribute to + // the cycle. + describe(out, false); + throw Cycle(out.str()); + } + // A peculiarity of boost::topological_sort() is that it emits results in + // REVERSE topological order: to get the result you want, you must + // traverse the SortedList using reverse iterators. + return VertexList(sorted.rbegin(), sorted.rend()); +} + +std::ostream& LLDependenciesBase::describe(std::ostream& out, bool full) const +{ + // Should never encounter this base-class implementation; may mean that + // the KEY type doesn't have a suitable ostream operator<<(). + out << ""; + return out; +} + +std::string LLDependenciesBase::describe(bool full) const +{ + // Just use the ostream-based describe() on a std::ostringstream. The + // implementation is here mostly so that we can avoid #include + // in the header file. + std::ostringstream out; + describe(out, full); + return out.str(); +} diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h new file mode 100644 index 0000000000..bd4bd7c96a --- /dev/null +++ b/indra/llcommon/lldependencies.h @@ -0,0 +1,779 @@ +/** + * @file lldependencies.h + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief LLDependencies: a generic mechanism for expressing "b must follow a, + * but precede c" + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLDEPENDENCIES_H) +#define LL_LLDEPENDENCIES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** +* Utilities +*****************************************************************************/ +/** + * generic range transformer: given a range acceptable to Boost.Range (e.g. a + * standard container, an iterator pair, ...) and a unary function to apply to + * each element of the range, make a corresponding range that lazily applies + * that function to each element on dereferencing. + */ +template +inline +boost::iterator_range::type> > +make_transform_range(const RANGE& range, FUNCTION function) +{ + // shorthand for the iterator type embedded in our return type + typedef boost::transform_iterator::type> + transform_iterator; + return boost::make_iterator_range(transform_iterator(boost::begin(range), function), + transform_iterator(boost::end(range), function)); +} + +/// non-const version of make_transform_range() +template +inline +boost::iterator_range::type> > +make_transform_range(RANGE& range, FUNCTION function) +{ + // shorthand for the iterator type embedded in our return type + typedef boost::transform_iterator::type> + transform_iterator; + return boost::make_iterator_range(transform_iterator(boost::begin(range), function), + transform_iterator(boost::end(range), function)); +} + +/** + * From any range compatible with Boost.Range, instantiate any class capable + * of accepting an iterator pair. + */ +template +struct instance_from_range: public TYPE +{ + template + instance_from_range(RANGE range): + TYPE(boost::begin(range), boost::end(range)) + {} +}; + +/***************************************************************************** +* LLDependencies +*****************************************************************************/ +/** + * LLDependencies components that should not be reinstantiated for each KEY, + * NODE specialization + */ +class LLDependenciesBase +{ +public: + virtual ~LLDependenciesBase() {} + + /** + * Exception thrown by sort() if there's a cycle + */ + struct Cycle: public std::runtime_error + { + Cycle(const std::string& what): std::runtime_error(what) {} + }; + + /** + * Provide a short description of this LLDependencies instance on the + * specified output stream, assuming that its KEY type has an operator<<() + * that works with std::ostream. + * + * Pass @a full as @c false to omit any keys without dependency constraints. + */ + virtual std::ostream& describe(std::ostream& out, bool full=true) const; + + /// describe() to a string + virtual std::string describe(bool full=true) const; + +protected: + typedef std::vector< std::pair > EdgeList; + typedef std::vector VertexList; + VertexList topo_sort(int vertices, const EdgeList& edges) const; + + /** + * refpair is specifically intended to capture a pair of references. This + * is better than std::pair because some implementations of + * std::pair's ctor accept const references to the two types. If the + * types are themselves references, this results in an illegal reference- + * to-reference. + */ + template + struct refpair + { + refpair(T1 value1, T2 value2): + first(value1), + second(value2) + {} + T1 first; + T2 second; + }; +}; + +/// describe() helper: for most types, report the type as usual +template +inline +std::ostream& LLDependencies_describe(std::ostream& out, const T& key) +{ + out << key; + return out; +} + +/// specialize LLDependencies_describe() for std::string +template<> +inline +std::ostream& LLDependencies_describe(std::ostream& out, const std::string& key) +{ + out << '"' << key << '"'; + return out; +} + +/** + * It's reasonable to use LLDependencies in a keys-only way, more or less like + * std::set. For that case, the default NODE type is an empty struct. + */ +struct LLDependenciesEmpty +{ + LLDependenciesEmpty() {} + /** + * Give it a constructor accepting void* so caller can pass placeholder + * values such as NULL or 0 rather than having to write + * LLDependenciesEmpty(). + */ + LLDependenciesEmpty(void*) {} +}; + +/** + * This class manages abstract dependencies between node types of your + * choosing. As with a std::map, nodes are copied when add()ed, so the node + * type should be relatively lightweight; to manipulate dependencies between + * expensive objects, use a pointer type. + * + * For a given node, you may state the keys of nodes that must precede it + * and/or nodes that must follow it. The sort() method will produce an order + * that should work, or throw an exception if the constraints are impossible. + * We cache results to minimize the cost of repeated sort() calls. + */ +template +class LLDependencies: public LLDependenciesBase +{ + typedef LLDependencies self_type; + + /** + * Internally, we bundle the client's NODE with its before/after keys. + */ + struct DepNode + { + typedef std::set dep_set; + DepNode(const NODE& node_, const dep_set& after_, const dep_set& before_): + node(node_), + after(after_), + before(before_) + {} + NODE node; + dep_set after, before; + }; + typedef std::map DepNodeMap; + typedef typename DepNodeMap::value_type DepNodeMapEntry; + + /// We have various ways to get the dependencies for a given DepNode. + /// Rather than having to restate each one for 'after' and 'before' + /// separately, pass a dep_selector so we can apply each to either. + typedef boost::function dep_selector; + +public: + LLDependencies() {} + + typedef KEY key_type; + typedef NODE node_type; + + /// param type used to express lists of other node keys -- note that such + /// lists can be initialized with boost::assign::list_of() + typedef std::vector KeyList; + + /** + * Add a new node. State its dependencies on other nodes (which may not + * yet have been added) by listing the keys of nodes this new one must + * follow, and separately the keys of nodes this new one must precede. + * + * The node you pass is @em copied into an internal data structure. If you + * want to modify the node value after add()ing it, capture the returned + * NODE& reference. + * + * @note + * Actual dependency analysis is deferred to the sort() method, so + * you can add an arbitrary number of nodes without incurring analysis + * overhead for each. The flip side of this is that add()ing nodes that + * define a cycle leaves this object in a state in which sort() will + * always throw the Cycle exception. + * + * Two distinct use cases are anticipated: + * * The data used to load this object are completely known at compile + * time (e.g. LLEventPump listener names). A Cycle exception represents a + * bug which can be corrected by the coder. The program need neither catch + * Cycle nor attempt to salvage the state of this object. + * * The data are loaded at runtime, therefore the universe of + * dependencies cannot be known at compile time. The client code should + * catch Cycle. + * ** If a Cycle exception indicates fatally-flawed input data, this + * object can simply be discarded, possibly with the entire program run. + * ** If it is essential to restore this object to a working state, the + * simplest workaround is to remove() nodes in LIFO order. + * *** It may be useful to add functionality to this class to track the + * add() chronology, providing a pop() method to remove the most recently + * added node. + * *** It may further be useful to add a restore() method which would + * pop() until sort() no longer throws Cycle. This method would be + * expensive -- but it's not clear that client code could resolve the + * problem more cheaply. + */ + NODE& add(const KEY& key, const NODE& node = NODE(), + const KeyList& after = KeyList(), + const KeyList& before = KeyList()) + { + // Get the passed-in lists as sets for equality comparison + typename DepNode::dep_set + after_set(after.begin(), after.end()), + before_set(before.begin(), before.end()); + // Try to insert the new node; if it already exists, find the old + // node instead. + std::pair inserted = + mNodes.insert(typename DepNodeMap::value_type(key, + DepNode(node, after_set, before_set))); + if (! inserted.second) // bool indicating success of insert() + { + // We already have a node by this name. Have its dependencies + // changed? If the existing node's dependencies are identical, the + // result will be unchanged, so we can leave the cache intact. + // Regardless of inserted.second, inserted.first is the iterator + // to the newly-inserted (or existing) map entry. Of course, that + // entry's second is the DepNode of interest. + if (inserted.first->second.after != after_set || + inserted.first->second.before != before_set) + { + // Dependencies have changed: clear the cached result. + mCache.clear(); + // save the new dependencies + inserted.first->second.after = after_set; + inserted.first->second.before = before_set; + } + } + else // this node is new + { + // This will change results. + mCache.clear(); + } + return inserted.first->second.node; + } + + /// the value of an iterator, showing both KEY and its NODE + typedef refpair value_type; + /// the value of a const_iterator + typedef refpair const_value_type; + +private: + // Extract functors + static value_type value_extract(DepNodeMapEntry& entry) + { + return value_type(entry.first, entry.second.node); + } + + static const_value_type const_value_extract(const DepNodeMapEntry& entry) + { + return const_value_type(entry.first, entry.second.node); + } + + // All the iterator access methods return iterator ranges just to cut down + // on the friggin' boilerplate!! + + /// generic mNodes range method + template + boost::iterator_range generic_range(FUNCTION function) + { + return make_transform_range(mNodes, function); + } + + /// generic mNodes const range method + template + boost::iterator_range generic_range(FUNCTION function) const + { + return make_transform_range(mNodes, function); + } + +public: + /// iterator over value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::iterator> iterator; + /// range over value_type entries + typedef boost::iterator_range range; + + /// iterate over value_type in @c KEY order rather than dependency order + range get_range() + { + return generic_range(value_extract); + } + + /// iterator over const_value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_iterator; + /// range over const_value_type entries + typedef boost::iterator_range const_range; + + /// iterate over const_value_type in @c KEY order rather than dependency order + const_range get_range() const + { + return generic_range(const_value_extract); + } + + /// iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::iterator> node_iterator; + /// range over stored NODEs + typedef boost::iterator_range node_range; + + /// iterate over NODE in @c KEY order rather than dependency order + node_range get_node_range() + { + // First take a DepNodeMapEntry and extract a reference to its + // DepNode, then from that extract a reference to its NODE. + return generic_range( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_node_iterator; + /// const range over stored NODEs + typedef boost::iterator_range const_node_range; + + /// iterate over const NODE in @c KEY order rather than dependency order + const_node_range get_node_range() const + { + // First take a DepNodeMapEntry and extract a reference to its + // DepNode, then from that extract a reference to its NODE. + return generic_range( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored KEYs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_key_iterator; + /// const range over stored KEYs + typedef boost::iterator_range const_key_range; + // We don't provide a non-const iterator over KEYs because they should be + // immutable, and in fact our underlying std::map won't give us non-const + // references. + + /// iterate over const KEY in @c KEY order rather than dependency order + const_key_range get_key_range() const + { + // From a DepNodeMapEntry, extract a reference to its KEY. + return generic_range( + boost::bind(&DepNodeMapEntry::first, _1)); + } + + /** + * Find an existing NODE, or return NULL. We decided to avoid providing a + * method analogous to std::map::find(), for a couple of reasons: + * + * * For a find-by-key, getting back an iterator to the (key, value) pair + * is less than useful, since you already have the key in hand. + * * For a failed request, comparing to end() is problematic. First, we + * provide range accessors, so it's more code to get end(). Second, we + * provide a number of different ranges -- quick, to which one's end() + * should we compare the iterator returned by find()? + * + * The returned pointer is solely to allow expressing the not-found + * condition. LLDependencies still owns the found NODE. + */ + const NODE* get(const KEY& key) const + { + typename DepNodeMap::const_iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + return &found->second.node; + } + return NULL; + } + + /** + * non-const get() + */ + NODE* get(const KEY& key) + { + // Use const implementation, then cast away const-ness of return + return const_cast(const_cast(this)->get(key)); + } + + /** + * Remove a node with specified key. This operation is the major reason + * we rebuild the graph on the fly instead of storing it. + */ + bool remove(const KEY& key) + { + typename DepNodeMap::iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + mNodes.erase(found); + return true; + } + return false; + } + +private: + /// cached list of iterators + typedef std::vector iterator_list; + typedef typename iterator_list::iterator iterator_list_iterator; + +public: + /** + * The return type of the sort() method needs some explanation. Provide a + * public typedef to facilitate storing the result. + * + * * We will prepare mCache by looking up DepNodeMap iterators. + * * We want to return a range containing iterators that will walk mCache. + * * If we simply stored DepNodeMap iterators and returned + * (mCache.begin(), mCache.end()), dereferencing each iterator would + * obtain a DepNodeMap iterator. + * * We want the caller to loop over @c value_type: pair. + * * This requires two transformations: + * ** mCache must contain @c LLDependencies::iterator so that + * dereferencing each entry will obtain an @c LLDependencies::value_type + * rather than a DepNodeMapEntry. + * ** We must wrap mCache's iterators in boost::indirect_iterator so that + * dereferencing one of our returned iterators will also dereference the + * iterator contained in mCache. + */ + typedef boost::iterator_range > sorted_range; + /// for convenience in looping over a sorted_range + typedef typename sorted_range::iterator sorted_iterator; + + /** + * Once we've loaded in the dependencies of interest, arrange them into an + * order that works -- or throw Cycle exception. + * + * Return an iterator range over (key, node) pairs that traverses them in + * the desired order. + */ + sorted_range sort() const + { + // Changes to mNodes cause us to clear our cache, so empty mCache + // means it's invalid and should be recomputed. However, if mNodes is + // also empty, then an empty mCache represents a valid order, so don't + // bother sorting. + if (mCache.empty() && ! mNodes.empty()) + { + // Construct a map of node keys to distinct vertex numbers -- even for + // nodes mentioned only in before/after constraints, that haven't yet + // been explicitly added. Rely on std::map rejecting a second attempt + // to insert the same key. Use the map's size() as the vertex number + // to get a distinct value for each successful insertion. + typedef std::map VertexMap; + VertexMap vmap; + // Nest each of these loops because !@#$%? MSVC warns us that its + // former broken behavior has finally been fixed -- and our builds + // treat warnings as errors. + { + for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); + nmi != nmend; ++nmi) + { + vmap.insert(typename VertexMap::value_type(nmi->first, vmap.size())); + for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), + aend = nmi->second.after.end(); + ai != aend; ++ai) + { + vmap.insert(typename VertexMap::value_type(*ai, vmap.size())); + } + for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), + bend = nmi->second.before.end(); + bi != bend; ++bi) + { + vmap.insert(typename VertexMap::value_type(*bi, vmap.size())); + } + } + } + // Define the edges. For this we must traverse mNodes again, mapping + // all the known key dependencies to integer pairs. + EdgeList edges; + { + for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); + nmi != nmend; ++nmi) + { + int thisnode = vmap[nmi->first]; + // after dependencies: build edges from the named node to this one + for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), + aend = nmi->second.after.end(); + ai != aend; ++ai) + { + edges.push_back(EdgeList::value_type(vmap[*ai], thisnode)); + } + // before dependencies: build edges from this node to the + // named one + for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), + bend = nmi->second.before.end(); + bi != bend; ++bi) + { + edges.push_back(EdgeList::value_type(thisnode, vmap[*bi])); + } + } + } + // Hide the gory details of our topological sort, since they shouldn't + // get reinstantiated for each distinct NODE type. + VertexList sorted(topo_sort(vmap.size(), edges)); + // Build the reverse of vmap to look up the key for each vertex + // descriptor. vmap contains exactly one entry for each distinct key, + // and we're certain that the associated int values are distinct + // indexes. The fact that they're not in order is irrelevant. + KeyList vkeys(vmap.size()); + for (typename VertexMap::const_iterator vmi = vmap.begin(), vmend = vmap.end(); + vmi != vmend; ++vmi) + { + vkeys[vmi->second] = vmi->first; + } + // Walk the sorted output list, building the result into mCache so + // we'll have it next time someone asks. + mCache.clear(); + for (VertexList::const_iterator svi = sorted.begin(), svend = sorted.end(); + svi != svend; ++svi) + { + // We're certain that vkeys[*svi] exists. However, there might not + // yet be a corresponding entry in mNodes. + self_type* non_const_this(const_cast(this)); + typename DepNodeMap::iterator found = non_const_this->mNodes.find(vkeys[*svi]); + if (found != non_const_this->mNodes.end()) + { + // Make an iterator of appropriate type. + mCache.push_back(iterator(found, value_extract)); + } + } + } + // Whether or not we've just recomputed mCache, it should now contain + // the results we want. Return a range of indirect_iterators over it + // so that dereferencing a returned iterator will dereference the + // iterator stored in mCache and directly reference the (key, node) + // pair. + boost::indirect_iterator + begin(mCache.begin()), + end(mCache.end()); + return sorted_range(begin, end); + } + + /// Override base-class describe() with actual implementation + virtual std::ostream& describe(std::ostream& out, bool full=true) const + { + typename DepNodeMap::const_iterator dmi(mNodes.begin()), dmend(mNodes.end()); + if (dmi != dmend) + { + std::string sep; + describe(out, sep, *dmi, full); + while (++dmi != dmend) + { + describe(out, sep, *dmi, full); + } + } + return out; + } + + /// describe() helper: report a DepNodeEntry + static std::ostream& describe(std::ostream& out, std::string& sep, + const DepNodeMapEntry& entry, bool full) + { + // If we were asked for a full report, describe every node regardless + // of whether it has dependencies. If we were asked to suppress + // independent nodes, describe this one if either after or before is + // non-empty. + if (full || (! entry.second.after.empty()) || (! entry.second.before.empty())) + { + out << sep; + sep = "\n"; + if (! entry.second.after.empty()) + { + out << "after "; + describe(out, entry.second.after); + out << " -> "; + } + LLDependencies_describe(out, entry.first); + if (! entry.second.before.empty()) + { + out << " -> before "; + describe(out, entry.second.before); + } + } + return out; + } + + /// describe() helper: report a dep_set + static std::ostream& describe(std::ostream& out, const typename DepNode::dep_set& keys) + { + out << '('; + typename DepNode::dep_set::const_iterator ki(keys.begin()), kend(keys.end()); + if (ki != kend) + { + LLDependencies_describe(out, *ki); + while (++ki != kend) + { + out << ", "; + LLDependencies_describe(out, *ki); + } + } + out << ')'; + return out; + } + + /// Iterator over the before/after KEYs on which a given NODE depends + typedef typename DepNode::dep_set::const_iterator dep_iterator; + /// range over the before/after KEYs on which a given NODE depends + typedef boost::iterator_range dep_range; + + /// dependencies access from key + dep_range get_dep_range_from_key(const KEY& key, const dep_selector& selector) const + { + typename DepNodeMap::const_iterator found = mNodes.find(key); + if (found != mNodes.end()) + { + return dep_range(selector(found->second)); + } + // We want to return an empty range. On some platforms a default- + // constructed range (e.g. dep_range()) does NOT suffice! The client + // is likely to try to iterate from boost::begin(range) to + // boost::end(range); yet these iterators might not be valid. Instead + // return a range over a valid, empty container. + static const typename DepNode::dep_set empty_deps; + return dep_range(empty_deps.begin(), empty_deps.end()); + } + + /// dependencies access from any one of our key-order iterators + template + dep_range get_dep_range_from_xform(const ITERATOR& iterator, const dep_selector& selector) const + { + return dep_range(selector(iterator.base()->second)); + } + + /// dependencies access from sorted_iterator + dep_range get_dep_range_from_sorted(const sorted_iterator& sortiter, + const dep_selector& selector) const + { + // sorted_iterator is a boost::indirect_iterator wrapping an mCache + // iterator, which we can obtain by sortiter.base(). Deferencing that + // gets us an mCache entry, an 'iterator' -- one of our traversal + // iterators -- on which we can use get_dep_range_from_xform(). + return get_dep_range_from_xform(*sortiter.base(), selector); + } + + /** + * Get a range over the after KEYs stored for the passed KEY or iterator, + * in arbitrary order. If you pass a nonexistent KEY, returns empty + * range -- same as a KEY with no after KEYs. Detect existence of a KEY + * using get() instead. + */ + template + dep_range get_after_range(const KEY_OR_ITER& key) const; + + /** + * Get a range over the before KEYs stored for the passed KEY or iterator, + * in arbitrary order. If you pass a nonexistent KEY, returns empty + * range -- same as a KEY with no before KEYs. Detect existence of a KEY + * using get() instead. + */ + template + dep_range get_before_range(const KEY_OR_ITER& key) const; + +private: + DepNodeMap mNodes; + mutable iterator_list mCache; +}; + +/** + * Functor to get a dep_range from a KEY or iterator -- generic case. If the + * passed value isn't one of our iterator specializations, assume it's + * convertible to the KEY type. + */ +template +struct LLDependencies_dep_range_from +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const KEY_ITER& key, + const SELECTOR& selector) + { + return deps.get_dep_range_from_key(key, selector); + } +}; + +/// Specialize LLDependencies_dep_range_from for our key-order iterators +template +struct LLDependencies_dep_range_from< boost::transform_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::transform_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_xform(iter, selector); + } +}; + +/// Specialize LLDependencies_dep_range_from for sorted_iterator +template +struct LLDependencies_dep_range_from< boost::indirect_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::indirect_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_sorted(iter, selector); + } +}; + +/// generic get_after_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_after_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::after, _1)); +} + +/// generic get_before_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_before_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::before, _1)); +} + +#endif /* ! defined(LL_LLDEPENDENCIES_H) */ diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index e8c95d0a76..8aa5b3b62e 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -432,7 +432,7 @@ namespace LLError Settings() : printLocation(false), defaultLevel(LLError::LEVEL_DEBUG), - crashFunction(NULL), + crashFunction(), timeFunction(NULL), fileRecorder(NULL), fixedBufferRecorder(NULL), @@ -600,12 +600,18 @@ namespace LLError s.printLocation = print; } - void setFatalFunction(FatalFunction f) + void setFatalFunction(const FatalFunction& f) { Settings& s = Settings::get(); s.crashFunction = f; } + FatalFunction getFatalFunction() + { + Settings& s = Settings::get(); + return s.crashFunction; + } + void setTimeFunction(TimeFunction f) { Settings& s = Settings::get(); diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index a55d706d2e..c9424f8a5e 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -35,7 +35,7 @@ #define LL_LLERRORCONTROL_H #include "llerror.h" - +#include "boost/function.hpp" #include class LLFixedBuffer; @@ -83,16 +83,38 @@ namespace LLError Control functions. */ - typedef void(*FatalFunction)(const std::string& message); + typedef boost::function FatalFunction; void crashAndLoop(const std::string& message); - // Default fatal funtion: access null pointer and loops forever + // Default fatal function: access null pointer and loops forever - void setFatalFunction(FatalFunction); + void setFatalFunction(const FatalFunction&); // The fatal function will be called when an message of LEVEL_ERROR // is logged. Note: supressing a LEVEL_ERROR message from being logged // (by, for example, setting a class level to LEVEL_NONE), will keep // the that message from causing the fatal funciton to be invoked. - + + FatalFunction getFatalFunction(); + // Retrieve the previously-set FatalFunction + + /// temporarily override the FatalFunction for the duration of a + /// particular scope, e.g. for unit tests + class OverrideFatalFunction + { + public: + OverrideFatalFunction(const FatalFunction& func): + mPrev(getFatalFunction()) + { + setFatalFunction(func); + } + ~OverrideFatalFunction() + { + setFatalFunction(mPrev); + } + + private: + FatalFunction mPrev; + }; + typedef std::string (*TimeFunction)(); std::string utcTime(); diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp index 24be6e8b34..f669d0e13f 100644 --- a/indra/llcommon/llevent.cpp +++ b/indra/llcommon/llevent.cpp @@ -34,6 +34,8 @@ #include "llevent.h" +using namespace LLOldEvents; + /************************************************ Events ************************************************/ diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 2b8f276df1..2cc8577219 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -38,6 +38,9 @@ #include "llpointer.h" #include "llthread.h" +namespace LLOldEvents +{ + class LLEventListener; class LLEvent; class LLEventDispatcher; @@ -194,4 +197,6 @@ public: LLSD mValue; }; +} // LLOldEvents + #endif // LL_EVENT_H diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp new file mode 100644 index 0000000000..eb380ba7c8 --- /dev/null +++ b/indra/llcommon/llevents.cpp @@ -0,0 +1,501 @@ +/** + * @file llevents.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Implementation for llevents. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// associated header +#include "llevents.h" +// STL headers +#include +#include +#include +// std headers +#include +#include +#include +#include +// external library headers +#include +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no +#endif +#include +#if LL_WINDOWS +#pragma warning (pop) +#endif +// other Linden headers + +/***************************************************************************** +* queue_names: specify LLEventPump names that should be instantiated as +* LLEventQueue +*****************************************************************************/ +/** + * At present, we recognize particular requested LLEventPump names as needing + * LLEventQueues. Later on we'll migrate this information to an external + * configuration file. + */ +const char* queue_names[] = +{ + "placeholder - replace with first real name string" +}; + +/***************************************************************************** +* If there's a "mainloop" pump, listen on that to flush all LLEventQueues +*****************************************************************************/ +struct RegisterFlush +{ + RegisterFlush(): + pumps(LLEventPumps::instance()), + mainloop(pumps.obtain("mainloop")), + name("flushLLEventQueues") + { + mainloop.listen(name, boost::bind(&RegisterFlush::flush, this, _1)); + } + bool flush(const LLSD&) + { + pumps.flush(); + return false; + } + ~RegisterFlush() + { + mainloop.stopListening(name); + } + LLEventPumps& pumps; + LLEventPump& mainloop; + const std::string name; +}; +static RegisterFlush registerFlush; + +/***************************************************************************** +* LLEventPumps +*****************************************************************************/ +LLEventPumps::LLEventPumps(): + // Until we migrate this information to an external config file, + // initialize mQueueNames from the static queue_names array. + mQueueNames(boost::begin(queue_names), boost::end(queue_names)) +{ +} + +LLEventPump& LLEventPumps::obtain(const std::string& name) +{ + PumpMap::iterator found = mPumpMap.find(name); + if (found != mPumpMap.end()) + { + // Here we already have an LLEventPump instance with the requested + // name. + return *found->second; + } + // Here we must instantiate an LLEventPump subclass. + LLEventPump* newInstance; + // Should this name be an LLEventQueue? + PumpNames::const_iterator nfound = mQueueNames.find(name); + if (nfound != mQueueNames.end()) + newInstance = new LLEventQueue(name); + else + newInstance = new LLEventStream(name); + // LLEventPump's constructor implicitly registers each new instance in + // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll + // delete it later. + mOurPumps.insert(newInstance); + return *newInstance; +} + +void LLEventPumps::flush() +{ + // Flush every known LLEventPump instance. Leave it up to each instance to + // decide what to do with the flush() call. + for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) + { + pmi->second->flush(); + } +} + +std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak) +{ + std::pair inserted = + mPumpMap.insert(PumpMap::value_type(name, const_cast(&pump))); + // If the insert worked, then the name is unique; return that. + if (inserted.second) + return name; + // Here the new entry was NOT inserted, and therefore name isn't unique. + // Unless we're permitted to tweak it, that's Bad. + if (! tweak) + { + throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'"); + } + // The passed name isn't unique, but we're permitted to tweak it. Find the + // first decimal-integer suffix not already taken. The insert() attempt + // above will have set inserted.first to the iterator of the existing + // entry by that name. Starting there, walk forward until we reach an + // entry that doesn't start with 'name'. For each entry consisting of name + // + integer suffix, capture the integer suffix in a set. Use a set + // because we're going to encounter string suffixes in the order: name1, + // name10, name11, name2, ... Walking those possibilities in that order + // isn't convenient to detect the first available "hole." + std::set suffixes; + PumpMap::iterator pmi(inserted.first), pmend(mPumpMap.end()); + // We already know inserted.first references the existing entry with + // 'name' as the key; skip that one and start with the next. + while (++pmi != pmend) + { + if (pmi->first.substr(0, name.length()) != name) + { + // Found the first entry beyond the entries starting with 'name': + // stop looping. + break; + } + // Here we're looking at an entry that starts with 'name'. Is the rest + // of it an integer? + // Dubious (?) assumption: in the local character set, decimal digits + // are in increasing order such that '9' is the last of them. This + // test deals with 'name' values such as 'a', where there might be a + // very large number of entries starting with 'a' whose suffixes + // aren't integers. A secondary assumption is that digit characters + // precede most common name characters (true in ASCII, false in + // EBCDIC). The test below is correct either way, but it's worth more + // if the assumption holds. + if (pmi->first[name.length()] > '9') + break; + // It should be cheaper to detect that we're not looking at a digit + // character -- and therefore the suffix can't possibly be an integer + // -- than to attempt the lexical_cast and catch the exception. + if (! std::isdigit(pmi->first[name.length()])) + continue; + // Okay, the first character of the suffix is a digit, it's worth at + // least attempting to convert to int. + try + { + suffixes.insert(boost::lexical_cast(pmi->first.substr(name.length()))); + } + catch (const boost::bad_lexical_cast&) + { + // If the rest of pmi->first isn't an int, just ignore it. + } + } + // Here we've accumulated in 'suffixes' all existing int suffixes of the + // entries starting with 'name'. Find the first unused one. + int suffix = 1; + for ( ; suffixes.find(suffix) != suffixes.end(); ++suffix) + ; + // Here 'suffix' is not in 'suffixes'. Construct a new name based on that + // suffix, insert it and return it. + std::ostringstream out; + out << name << suffix; + return registerNew(pump, out.str(), tweak); +} + +void LLEventPumps::unregister(const LLEventPump& pump) +{ + // Remove this instance from mPumpMap + PumpMap::iterator found = mPumpMap.find(pump.getName()); + if (found != mPumpMap.end()) + { + mPumpMap.erase(found); + } + // If this instance is one we created, also remove it from mOurPumps so we + // won't try again to delete it later! + PumpSet::iterator psfound = mOurPumps.find(const_cast(&pump)); + if (psfound != mOurPumps.end()) + { + mOurPumps.erase(psfound); + } +} + +LLEventPumps::~LLEventPumps() +{ + // On destruction, delete every LLEventPump we instantiated (via + // obtain()). CAREFUL: deleting an LLEventPump calls its destructor, which + // calls unregister(), which removes that LLEventPump instance from + // mOurPumps. So an iterator loop over mOurPumps to delete contained + // LLEventPump instances is dangerous! Instead, delete them one at a time + // until mOurPumps is empty. + while (! mOurPumps.empty()) + { + delete *mOurPumps.begin(); + } +} + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +LLEventPump::LLEventPump(const std::string& name, bool tweak): + // Register every new instance with LLEventPumps + mName(LLEventPumps::instance().registerNew(*this, name, tweak)), + mEnabled(true) +{} + +#if LL_WINDOWS +#pragma warning (pop) +#endif + +LLEventPump::~LLEventPump() +{ + // Unregister this doomed instance from LLEventPumps + LLEventPumps::instance().unregister(*this); +} + +// static data member +const LLEventPump::NameList LLEventPump::empty; + +LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, + const NameList& after, + const NameList& before) +{ + // Check for duplicate name before connecting listener to mSignal + ConnectionMap::const_iterator found = mConnections.find(name); + // In some cases the user might disconnect a connection explicitly -- or + // might use LLEventTrackable to disconnect implicitly. Either way, we can + // end up retaining in mConnections a zombie connection object that's + // already been disconnected. Such a connection object can't be + // reconnected -- nor, in the case of LLEventTrackable, would we want to + // try, since disconnection happens with the destruction of the listener + // object. That means it's safe to overwrite a disconnected connection + // object with the new one we're attempting. The case we want to prevent + // is only when the existing connection object is still connected. + if (found != mConnections.end() && found->second.connected()) + { + throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name + + "' on " + typeid(*this).name() + " '" + getName() + "'"); + } + // Okay, name is unique, try to reconcile its dependencies. Specify a new + // "node" value that we never use for an mSignal placement; we'll fix it + // later. + DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before); + // What if this listener has been added, removed and re-added? In that + // case newNode already has a non-negative value because we never remove a + // listener from mDeps. But keep processing uniformly anyway in case the + // listener was added back with different dependencies. Then mDeps.sort() + // would put it in a different position, and the old newNode placement + // value would be wrong, so we'd have to reassign it anyway. Trust that + // re-adding a listener with the same dependencies is the trivial case for + // mDeps.sort(): it can just replay its cache. + DependencyMap::sorted_range sorted_range; + try + { + // Can we pick an order that works including this new entry? + sorted_range = mDeps.sort(); + } + catch (const DependencyMap::Cycle& e) + { + // No: the new node's after/before dependencies have made mDeps + // unsortable. If we leave the new node in mDeps, it will continue + // to screw up all future attempts to sort()! Pull it out. + mDeps.remove(name); + throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() + + " '" + getName() + "' would cause cycle: " + e.what()); + } + // Walk the list to verify that we haven't changed the order. + float previous = 0.0, myprev = 0.0; + DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop + for (DependencyMap::sorted_iterator dmi = sorted_range.begin(); + dmi != sorted_range.end(); ++dmi) + { + // Since we've added the new entry with an invalid placement, + // recognize it and skip it. + if (dmi->first == name) + { + // Remember the iterator belonging to our new node, and which + // placement value was 'previous' at that point. + mydmi = dmi; + myprev = previous; + continue; + } + // If the new node has rearranged the existing nodes, we'll find + // that their placement values are no longer in increasing order. + if (dmi->second < previous) + { + // This is another scenario in which we'd better back out the + // newly-added node from mDeps -- but don't do it yet, we want to + // traverse the existing mDeps to report on it! + // Describe the change to the order of our listeners. Copy + // everything but the newest listener to a vector we can sort to + // obtain the old order. + typedef std::vector< std::pair > SortNameList; + SortNameList sortnames; + for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end()); + cdmi != cdmend; ++cdmi) + { + if (cdmi->first != name) + { + sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first)); + } + } + std::sort(sortnames.begin(), sortnames.end()); + std::ostringstream out; + out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName() + << "' would move previous listener '" << dmi->first << "'\nwas: "; + SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end()); + if (sni != snend) + { + out << sni->second; + while (++sni != snend) + { + out << ", " << sni->second; + } + } + out << "\nnow: "; + DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end()); + if (ddmi != ddmend) + { + out << ddmi->first; + while (++ddmi != ddmend) + { + out << ", " << ddmi->first; + } + } + // NOW remove the offending listener node. + mDeps.remove(name); + // Having constructed a description of the order change, inform caller. + throw OrderChange(out.str()); + } + // This node becomes the previous one. + previous = dmi->second; + } + // We just got done with a successful mDeps.add(name, ...) call. We'd + // better have found 'name' somewhere in that sorted list! + assert(mydmi != sorted_range.end()); + // Four cases: + // 0. name is the only entry: placement 1.0 + // 1. name is the first of several entries: placement (next placement)/2 + // 2. name is between two other entries: placement (myprev + (next placement))/2 + // 3. name is the last entry: placement ceil(myprev) + 1.0 + // Since we've cleverly arranged for myprev to be 0.0 if name is the + // first entry, this folds down to two cases. Case 1 is subsumed by + // case 2, and case 0 is subsumed by case 3. So we need only handle + // cases 2 and 3, which means we need only detect whether name is the + // last entry. Increment mydmi to see if there's anything beyond. + if (++mydmi != sorted_range.end()) + { + // The new node isn't last. Place it between the previous node and + // the successor. + newNode = (myprev + mydmi->second)/2.0; + } + else + { + // The new node is last. Bump myprev up to the next integer, add + // 1.0 and use that. + newNode = std::ceil(myprev) + 1.0; + } + // Now that newNode has a value that places it appropriately in mSignal, + // connect it. + LLBoundListener bound = mSignal.connect(newNode, listener); + mConnections[name] = bound; + return bound; +} + +LLBoundListener LLEventPump::getListener(const std::string& name) const +{ + ConnectionMap::const_iterator found = mConnections.find(name); + if (found != mConnections.end()) + { + return found->second; + } + // not found, return dummy LLBoundListener + return LLBoundListener(); +} + +void LLEventPump::stopListening(const std::string& name) +{ + ConnectionMap::iterator found = mConnections.find(name); + if (found != mConnections.end()) + { + found->second.disconnect(); + mConnections.erase(found); + } + // We intentionally do NOT remove this name from mDeps. It may happen that + // the same listener with the same name and dependencies will jump on and + // off this LLEventPump repeatedly. Keeping a cache of dependencies will + // avoid a new dependency sort in such cases. +} + +/***************************************************************************** +* LLEventStream +*****************************************************************************/ +bool LLEventStream::post(const LLSD& event) +{ + if (! mEnabled) + return false; + // Let caller know if any one listener handled the event. This is mostly + // useful when using LLEventStream as a listener for an upstream + // LLEventPump. + return mSignal(event); +} + +/***************************************************************************** +* LLEventQueue +*****************************************************************************/ +bool LLEventQueue::post(const LLSD& event) +{ + if (mEnabled) + { + // Defer sending this event by queueing it until flush() + mEventQueue.push_back(event); + } + // Unconditionally return false. We won't know until flush() whether a + // listener claims to have handled the event -- meanwhile, don't block + // other listeners. + return false; +} + +void LLEventQueue::flush() +{ + // Consider the case when a given listener on this LLEventQueue posts yet + // another event on the same queue. If we loop over mEventQueue directly, + // we'll end up processing all those events during the same flush() call + // -- rather like an EventStream. Instead, copy mEventQueue and clear it, + // so that any new events posted to this LLEventQueue during flush() will + // be processed in the *next* flush() call. + EventQueue queue(mEventQueue); + mEventQueue.clear(); + for ( ; ! queue.empty(); queue.pop_front()) + { + mSignal(queue.front()); + } +} + +/***************************************************************************** +* LLListenerOrPumpName +*****************************************************************************/ +LLListenerOrPumpName::LLListenerOrPumpName(const std::string& pumpname): + // Look up the specified pumpname, and bind its post() method as our listener + mListener(boost::bind(&LLEventPump::post, + boost::ref(LLEventPumps::instance().obtain(pumpname)), + _1)) +{ +} + +LLListenerOrPumpName::LLListenerOrPumpName(const char* pumpname): + // Look up the specified pumpname, and bind its post() method as our listener + mListener(boost::bind(&LLEventPump::post, + boost::ref(LLEventPumps::instance().obtain(pumpname)), + _1)) +{ +} + +bool LLListenerOrPumpName::operator()(const LLSD& event) const +{ + if (! mListener) + { + throw Empty("attempting to call uninitialized"); + } + return (*mListener)(event); +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h new file mode 100644 index 0000000000..2f6515a4cb --- /dev/null +++ b/indra/llcommon/llevents.h @@ -0,0 +1,822 @@ +/** + * @file llevents.h + * @author Kent Quirk, Nat Goodspeed + * @date 2008-09-11 + * @brief This is an implementation of the event system described at + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System, + * originally introduced in llnotifications.h. It has nothing + * whatsoever to do with the older system in llevent.h. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTS_H) +#define LL_LLEVENTS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // noncopyable +#include +#include +#include +#include // reference_wrapper +#include +#include +#include +#include +#include +#include +#include "llsd.h" +#include "llsingleton.h" +#include "lldependencies.h" + +// override this to allow binding free functions with more parameters +#ifndef LLEVENTS_LISTENER_ARITY +#define LLEVENTS_LISTENER_ARITY 10 +#endif + +// hack for testing +#ifndef testable +#define testable private +#endif + +/***************************************************************************** +* Signal and handler declarations +* Using a single handler signature means that we can have a common handler +* type, rather than needing a distinct one for each different handler. +*****************************************************************************/ + +/** + * A boost::signals Combiner that stops the first time a handler returns true + * We need this because we want to have our handlers return bool, so that + * we have the option to cause a handler to stop further processing. The + * default handler fails when the signal returns a value but has no slots. + */ +struct LLStopWhenHandled +{ + typedef bool result_type; + + template + result_type operator()(InputIterator first, InputIterator last) const + { + for (InputIterator si = first; si != last; ++si) + { + if (*si) + { + return true; + } + } + return false; + } +}; + +/** + * We want to have a standard signature for all signals; this way, + * we can easily document a protocol for communicating across + * dlls and into scripting languages someday. + * + * We want to return a bool to indicate whether the signal has been + * handled and should NOT be passed on to other listeners. + * Return true to stop further handling of the signal, and false + * to continue. + * + * We take an LLSD because this way the contents of the signal + * are independent of the API used to communicate it. + * It is const ref because then there's low cost to pass it; + * if you only need to inspect it, it's very cheap. + * + * @internal + * The @c float template parameter indicates that we will internally use @c + * float to indicate relative listener order on a given LLStandardSignal. + * Don't worry, the @c float values are strictly internal! They are not part + * of the interface, for the excellent reason that requiring the caller to + * specify a numeric key to establish order means that the caller must know + * the universe of possible values. We use LLDependencies for that instead. + */ +typedef boost::signals2::signal LLStandardSignal; +/// Methods that forward listeners (e.g. constructed with +/// boost::bind()) should accept (const LLEventListener&) +typedef LLStandardSignal::slot_type LLEventListener; +/// Result of registering a listener, supports connected(), +/// disconnect() and blocked() +typedef boost::signals2::connection LLBoundListener; + +/** + * A common idiom for event-based code is to accept either a callable -- + * directly called on completion -- or the string name of an LLEventPump on + * which to post the completion event. Specifying a parameter as const + * LLListenerOrPumpName& allows either. + * + * Calling a validly-constructed LLListenerOrPumpName, passing the LLSD + * 'event' object, either calls the callable or posts the event to the named + * LLEventPump. + * + * A default-constructed LLListenerOrPumpName is 'empty'. (This is useful as + * the default value of an optional method parameter.) Calling it throws + * LLListenerOrPumpName::Empty. Test for this condition beforehand using + * either if (param) or if (! param). + */ +class LLListenerOrPumpName +{ +public: + /// passing string name of LLEventPump + LLListenerOrPumpName(const std::string& pumpname); + /// passing string literal (overload so compiler isn't forced to infer + /// double conversion) + LLListenerOrPumpName(const char* pumpname); + /// passing listener -- the "anything else" catch-all case. The type of an + /// object constructed by boost::bind() isn't intended to be written out. + /// Normally we'd just accept 'const LLEventListener&', but that would + /// require double implicit conversion: boost::bind() object to + /// LLEventListener, LLEventListener to LLListenerOrPumpName. So use a + /// template to forward anything. + template + LLListenerOrPumpName(const T& listener): mListener(listener) {} + + /// for omitted method parameter: uninitialized mListener + LLListenerOrPumpName() {} + + /// test for validity + operator bool() const { return bool(mListener); } + bool operator! () const { return ! mListener; } + + /// explicit accessor + const LLEventListener& getListener() const { return *mListener; } + + /// implicit conversion to LLEventListener + operator LLEventListener() const { return *mListener; } + + /// allow calling directly + bool operator()(const LLSD& event) const; + + /// exception if you try to call when empty + struct Empty: public std::runtime_error + { + Empty(const std::string& what): + std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {} + }; + +private: + boost::optional mListener; +}; + +/***************************************************************************** +* LLEventPumps +*****************************************************************************/ +class LLEventPump; + +/** + * LLEventPumps is a Singleton manager through which one typically accesses + * this subsystem. + */ +class LLEventPumps: public LLSingleton +{ + friend class LLSingleton; +public: + /** + * Find or create an LLEventPump instance with a specific name. We return + * a reference so there's no question about ownership. obtain() @em finds + * an instance without conferring @em ownership. + */ + LLEventPump& obtain(const std::string& name); + /** + * Flush all known LLEventPump instances + */ + void flush(); + +private: + friend class LLEventPump; + /** + * Register a new LLEventPump instance (internal) + */ + std::string registerNew(const LLEventPump&, const std::string& name, bool tweak); + /** + * Unregister a doomed LLEventPump instance (internal) + */ + void unregister(const LLEventPump&); + +private: + LLEventPumps(); + ~LLEventPumps(); + +testable: + // Map of all known LLEventPump instances, whether or not we instantiated + // them. We store a plain old LLEventPump* because this map doesn't claim + // ownership of the instances. Though the common usage pattern is to + // request an instance using obtain(), it's fair to instantiate an + // LLEventPump subclass statically, as a class member, on the stack or on + // the heap. In such cases, the instantiating party is responsible for its + // lifespan. + typedef std::map PumpMap; + PumpMap mPumpMap; + // Set of all LLEventPumps we instantiated. Membership in this set means + // we claim ownership, and will delete them when this LLEventPumps is + // destroyed. + typedef std::set PumpSet; + PumpSet mOurPumps; + // LLEventPump names that should be instantiated as LLEventQueue rather + // than as LLEventStream + typedef std::set PumpNames; + PumpNames mQueueNames; +}; + +/***************************************************************************** +* details +*****************************************************************************/ +namespace LLEventDetail +{ + /// Any callable capable of connecting an LLEventListener to an + /// LLStandardSignal to produce an LLBoundListener can be mapped to this + /// signature. + typedef boost::function ConnectFunc; + + /** + * Utility template function to use Visitor appropriately + * + * @param listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_func Callable that will connect() @a listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const LISTENER& listener, + const ConnectFunc& connect_func); +} // namespace LLEventDetail + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +/** + * LLEventPump is the base class interface through which we access the + * concrete subclasses LLEventStream and LLEventQueue. + */ +class LLEventPump: boost::noncopyable +{ +public: + /** + * Exception thrown by LLEventPump(). You are trying to instantiate an + * LLEventPump (subclass) using the same name as some other instance, and + * you didn't pass tweak=true to permit it to generate a unique + * variant. + */ + struct DupPumpName: public std::runtime_error + { + DupPumpName(const std::string& what): + std::runtime_error(std::string("DupPumpName: ") + what) {} + }; + + /** + * Instantiate an LLEventPump (subclass) with the string name by which it + * can be found using LLEventPumps::obtain(). + * + * If you pass (or default) @a tweak to @c false, then a duplicate name + * will throw DupPumpName. This won't happen if LLEventPumps::obtain() + * instantiates the LLEventPump, because obtain() uses find-or-create + * logic. It can only happen if you instantiate an LLEventPump in your own + * code -- and a collision with the name of some other LLEventPump is + * likely to cause much more subtle problems! + * + * When you hand-instantiate an LLEventPump, consider passing @a tweak as + * @c true. This directs LLEventPump() to append a suffix to the passed @a + * name to make it unique. You can retrieve the adjusted name by calling + * getName() on your new instance. + */ + LLEventPump(const std::string& name, bool tweak=false); + virtual ~LLEventPump(); + + /// group exceptions thrown by listen(). We use exceptions because these + /// particular errors are likely to be coding errors, found and fixed by + /// the developer even before preliminary checkin. + struct ListenError: public std::runtime_error + { + ListenError(const std::string& what): std::runtime_error(what) {} + }; + /** + * exception thrown by listen(). You are attempting to register a + * listener on this LLEventPump using the same listener name as an + * already-registered listener. + */ + struct DupListenerName: public ListenError + { + DupListenerName(const std::string& what): + ListenError(std::string("DupListenerName: ") + what) + {} + }; + /** + * exception thrown by listen(). The order dependencies specified for your + * listener are incompatible with existing listeners. + * + * Consider listener "a" which specifies before "b" and "b" which + * specifies before "c". You are now attempting to register "c" before + * "a". There is no order that can satisfy all constraints. + */ + struct Cycle: public ListenError + { + Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {} + }; + /** + * exception thrown by listen(). This one means that your new listener + * would force a change to the order of previously-registered listeners, + * and we don't have a good way to implement that. + * + * Consider listeners "some", "other" and "third". "some" and "other" are + * registered earlier without specifying relative order, so "other" + * happens to be first. Now you attempt to register "third" after "some" + * and before "other". Whoops, that would require swapping "some" and + * "other", which we can't do. Instead we throw this exception. + * + * It may not be possible to change the registration order so we already + * know "third"s order requirement by the time we register the second of + * "some" and "other". A solution would be to specify that "some" must + * come before "other", or equivalently that "other" must come after + * "some". + */ + struct OrderChange: public ListenError + { + OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {} + }; + + /// used by listen() + typedef std::vector NameList; + /// convenience placeholder for when you explicitly want to pass an empty + /// NameList + const static NameList empty; + + /// Get this LLEventPump's name + std::string getName() const { return mName; } + + /** + * Register a new listener with a unique name. Specify an optional list + * of other listener names after which this one must be called, likewise + * an optional list of other listener names before which this one must be + * called. The other listeners mentioned need not yet be registered + * themselves. listen() can throw any ListenError; see ListenError + * subclasses. + * + * If (as is typical) you pass a boost::bind() expression, + * listen() will inspect the components of that expression. If a bound + * object matches any of several cases, the connection will automatically + * be disconnected when that object is destroyed. + * + * * You bind a boost::weak_ptr. + * * Binding a boost::shared_ptr that way would ensure that the + * referenced object would @em never be destroyed, since the @c + * shared_ptr stored in the LLEventPump would remain an outstanding + * reference. Use the weaken() function to convert your @c shared_ptr to + * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr + * will produce a compile error (@c BOOST_STATIC_ASSERT failure). + * * You bind a simple pointer or reference to an object derived from + * boost::enable_shared_from_this. (UNDER CONSTRUCTION) + * * You bind a simple pointer or reference to an object derived from + * LLEventTrackable. Unlike the cases described above, though, this is + * vulnerable to a couple of cross-thread race conditions, as described + * in the LLEventTrackable documentation. + */ + template + LLBoundListener listen(const std::string& name, const LISTENER& listener, + const NameList& after=NameList(), + const NameList& before=NameList()) + { + // Examine listener, using our listen_impl() method to make the + // actual connection. + // This is why listen() is a template. Conversion from boost::bind() + // to LLEventListener performs type erasure, so it's important to look + // at the boost::bind object itself before that happens. + return LLEventDetail::visit_and_connect(listener, + boost::bind(&LLEventPump::listen_impl, + this, + name, + _1, + after, + before)); + } + + /// Get the LLBoundListener associated with the passed name (dummy + /// LLBoundListener if not found) + virtual LLBoundListener getListener(const std::string& name) const; + /** + * Instantiate one of these to block an existing connection: + * @code + * { // in some local scope + * LLEventPump::Blocker block(someLLBoundListener); + * // code that needs the connection blocked + * } // unblock the connection again + * @endcode + */ + typedef boost::signals2::shared_connection_block Blocker; + /// Unregister a listener by name. Prefer this to + /// getListener(name).disconnect() because stopListening() also + /// forgets this name. + virtual void stopListening(const std::string& name); + /// Post an event to all listeners. The @c bool return is only meaningful + /// if the underlying leaf class is LLEventStream -- beware of relying on + /// it too much! Truthfully, we return @c bool mostly to permit chaining + /// one LLEventPump as a listener on another. + virtual bool post(const LLSD&) = 0; + /// Enable/disable: while disabled, silently ignore all post() calls + virtual void enable(bool enabled=true) { mEnabled = enabled; } + /// query + virtual bool enabled() const { return mEnabled; } + +private: + friend class LLEventPumps; + /// flush queued events + virtual void flush() {} + +private: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + std::string mName; + +protected: + /// implement the dispatching + LLStandardSignal mSignal; + /// valve open? + bool mEnabled; + /// Map of named listeners. This tracks the listeners that actually exist + /// at this moment. When we stopListening(), we discard the entry from + /// this map. + typedef std::map ConnectionMap; + ConnectionMap mConnections; + typedef LLDependencies DependencyMap; + /// Dependencies between listeners. For each listener, track the float + /// used to establish its place in mSignal's order. This caches all the + /// listeners that have ever registered; stopListening() does not discard + /// the entry from this map. This is to avoid a new dependency sort if the + /// same listener with the same dependencies keeps hopping on and off this + /// LLEventPump. + DependencyMap mDeps; +}; + +/***************************************************************************** +* LLEventStream +*****************************************************************************/ +/** + * LLEventStream is a thin wrapper around LLStandardSignal. Posting an + * event immediately calls all registered listeners. + */ +class LLEventStream: public LLEventPump +{ +public: + LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventStream() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); +}; + +/***************************************************************************** +* LLEventQueue +*****************************************************************************/ +/** + * LLEventQueue isa LLEventPump whose post() method defers calling registered + * listeners until flush() is called. + */ +class LLEventQueue: public LLEventPump +{ +public: + LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventQueue() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); + +private: + /// flush queued events + virtual void flush(); + +private: + typedef std::deque EventQueue; + EventQueue mEventQueue; +}; + +/***************************************************************************** +* LLEventTrackable and underpinnings +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1)). This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * LLEventPump::listen(). + * - The @c MySubclass object begins destruction. ~MySubclass() + * runs, destroying state specific to the subclass. (For instance, a + * Foo* data member is deleted but not zeroed.) + * - The listening method will not be disconnected until + * ~LLEventTrackable() runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the Foo* pointer that was + * deleted but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with boost::shared_ptr. + * Passing LLEventPump::listen() a boost::bind() expression + * involving a boost::weak_ptr is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + +/** + * We originally provided a suite of overloaded + * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call + * LLEventPump::listen(...) and then pass the returned LLBoundListener to + * LLEventTrackable::track(). This was workable but error-prone: the coder + * must remember to call listenTo() rather than the more straightforward + * listen() method. + * + * Now we publish only the single canonical listen() method, so there's a + * uniform mechanism. Having a single way to do this is good, in that there's + * no question in the coder's mind which of several alternatives to choose. + * + * To support automatic connection management, we use boost::visit_each + * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to + * inspect each argument of a boost::bind expression. (Although the visit_each + * mechanism was first introduced with the original Boost.Signals library, it + * was only later documented.) + * + * Cases: + * * At least one of the function's arguments is a boost::weak_ptr. Pass + * the corresponding shared_ptr to slot_type::track(). Ideally that would be + * the object whose method we want to call, but in fact we do the same for + * any weak_ptr we might find among the bound arguments. If we're passing + * our bound method a weak_ptr to some object, wouldn't the destruction of + * that object invalidate the call? So we disconnect automatically when any + * such object is destroyed. This is the mechanism preferred by boost:: + * signals2. + * * One of the functions's arguments is a boost::shared_ptr. This produces + * a compile error: the bound copy of the shared_ptr stored in the + * boost_bind object stored in the signal object would make the referenced + * T object immortal. We provide a weaken() function. Pass + * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the + * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr + * implicitly and just proceed.) + * * One of the function's arguments is a plain pointer/reference to an object + * derived from boost::enable_shared_from_this. We assume that this object + * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr + * and track that. (UNDER CONSTRUCTION) + * * One of the function's arguments is derived from LLEventTrackable. Pass + * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable + * to a couple different race conditions, as described in LLEventTrackable + * documentation. (NOTE: Now that LLEventTrackable is a typedef for + * boost::signals2::trackable, the Signals2 library handles this itself, so + * our visitor needs no special logic for this case.) + * * Any other argument type is irrelevant to automatic connection management. + */ + +namespace LLEventDetail +{ + template + const F& unwrap(const F& f) { return f; } + + template + const F& unwrap(const boost::reference_wrapper& f) { return f.get(); } + + // Most of the following is lifted from the Boost.Signals use of + // visit_each. + template struct truth {}; + + /** + * boost::visit_each() Visitor, used on a template argument const F& + * f as follows (see visit_and_connect()): + * @code + * LLEventListener listener(f); + * Visitor visitor(listener); // bind listener so it can track() shared_ptrs + * using boost::visit_each; // allow unqualified visit_each() call for ADL + * visit_each(visitor, unwrap(f)); + * @endcode + */ + class Visitor + { + public: + /** + * Visitor binds a reference to LLEventListener so we can track() any + * shared_ptrs we find in the argument list. + */ + Visitor(LLEventListener& listener): + mListener(listener) + { + } + + /** + * boost::visit_each() calls this method for each component of a + * boost::bind() expression. + */ + template + void operator()(const T& t) const + { + decode(t, 0); + } + + private: + // decode() decides between a reference wrapper and anything else + // boost::ref() variant + template + void decode(const boost::reference_wrapper& t, int) const + { +// add_if_trackable(t.get_pointer()); + } + + // decode() anything else + template + void decode(const T& t, long) const + { + typedef truth<(boost::is_pointer::value)> is_a_pointer; + maybe_get_pointer(t, is_a_pointer()); + } + + // maybe_get_pointer() decides between a pointer and a non-pointer + // plain pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { +// add_if_trackable(t); + } + + // shared_ptr variant + template + void maybe_get_pointer(const boost::shared_ptr& t, truth) const + { + // If we have a shared_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. +// mListener.track(t); + + // Make this case illegal. Passing a bound shared_ptr to + // slot_type::track() is useless, since the bound shared_ptr will + // keep the object alive anyway! Force the coder to cast to weak_ptr. + + // Trivial as it is, make the BOOST_STATIC_ASSERT() condition + // dependent on template param so the macro is only evaluated if + // this method is in fact instantiated, as described here: + // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html + + // ATTENTION: Don't bind a shared_ptr using + // LLEventPump::listen(boost::bind()). Doing so captures a copy of + // the shared_ptr, making the referenced object effectively + // immortal. Use the weaken() function, e.g.: + // somepump.listen(boost::bind(...weaken(my_shared_ptr)...)); + // This lets us automatically disconnect when the referenced + // object is destroyed. + BOOST_STATIC_ASSERT(sizeof(T) == 0); + } + + // weak_ptr variant + template + void maybe_get_pointer(const boost::weak_ptr& t, truth) const + { + // If we have a weak_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. + mListener.track(t); +// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n"; + } + +#if 0 + // reference to anything derived from boost::enable_shared_from_this + template + inline void maybe_get_pointer(const boost::enable_shared_from_this& ct, + truth) const + { + // Use the slot_type::track(shared_ptr) mechanism. Cast away + // const-ness because (in our code base anyway) it's unusual + // to find shared_ptr. + boost::enable_shared_from_this& + t(const_cast&>(ct)); + std::cout << "Capturing shared_from_this()" << std::endl; + boost::shared_ptr sp(t.shared_from_this()); +/*==========================================================================*| + std::cout << "Capturing weak_ptr" << std::endl; + boost::weak_ptr wp(sp); +|*==========================================================================*/ + std::cout << "Tracking shared__ptr" << std::endl; + mListener.track(sp); + } +#endif + + // non-pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { + // Take the address of this object, because the object itself may be + // trackable +// add_if_trackable(boost::addressof(t)); + } + +/*==========================================================================*| + // add_if_trackable() adds LLEventTrackable objects to mTrackables + inline void add_if_trackable(const LLEventTrackable* t) const + { + if (t) + { + } + } + + // pointer to anything not an LLEventTrackable subclass + inline void add_if_trackable(const void*) const + { + } + + // pointer to free function + // The following construct uses the preprocessor to generate + // add_if_trackable() overloads accepting pointer-to-function taking + // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type. +#define BOOST_PP_LOCAL_MACRO(n) \ + template \ + inline void \ + add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \ + { \ + } +#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY) +#include BOOST_PP_LOCAL_ITERATE() +#undef BOOST_PP_LOCAL_MACRO +#undef BOOST_PP_LOCAL_LIMITS +|*==========================================================================*/ + + /// Bind a reference to the LLEventListener to call its track() method. + LLEventListener& mListener; + }; + + /** + * Utility template function to use Visitor appropriately + * + * @param raw_listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_funct Callable that will connect() @a raw_listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const LISTENER& raw_listener, + const ConnectFunc& connect_func) + { + // Capture the listener + LLEventListener listener(raw_listener); + // Define our Visitor, binding the listener so we can call + // listener.track() if we discover any shared_ptr. + LLEventDetail::Visitor visitor(listener); + // Allow unqualified visit_each() call for ADL + using boost::visit_each; + // Visit each component of a boost::bind() expression. Pass + // 'raw_listener', our template argument, rather than 'listener' from + // which type details have been erased. unwrap() comes from + // Boost.Signals, in case we were passed a boost::ref(). + visit_each(visitor, LLEventDetail::unwrap(raw_listener)); + // Make the connection using passed function. At present, wrapping + // this functionality into this function is a bit silly: we don't + // really need a visit_and_connect() function any more, just a visit() + // function. The definition of this function dates from when, after + // visit_each(), after establishing the connection, we had to + // postprocess the new connection with the visitor object. That's no + // longer necessary. + return connect_func(listener); + } +} // namespace LLEventDetail + +// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr...) to +// listen() fails in Boost code trying to instantiate LLEventListener (i.e. +// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't +// specialized for boost::weak_ptr. This remedies that omission. +namespace boost +{ + template + T* get_pointer(const weak_ptr& ptr) { return shared_ptr(ptr).get(); } +} + +/// Since we forbid use of listen(boost::bind(...shared_ptr...)), provide an +/// easy way to cast to the corresponding weak_ptr. +template +boost::weak_ptr weaken(const boost::shared_ptr& ptr) +{ + return boost::weak_ptr(ptr); +} + +#endif /* ! defined(LL_LLEVENTS_H) */ diff --git a/indra/llcommon/lllazy.cpp b/indra/llcommon/lllazy.cpp new file mode 100644 index 0000000000..215095bc27 --- /dev/null +++ b/indra/llcommon/lllazy.cpp @@ -0,0 +1,23 @@ +/** + * @file lllazy.cpp + * @author Nat Goodspeed + * @date 2009-01-28 + * @brief Implementation for lllazy. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lllazy.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// lllazy.h is presently header-only. This file exists only because our CMake +// test macro ADD_BUILD_TEST requires it. +int dummy = 0; diff --git a/indra/llcommon/lllazy.h b/indra/llcommon/lllazy.h new file mode 100644 index 0000000000..2240954d98 --- /dev/null +++ b/indra/llcommon/lllazy.h @@ -0,0 +1,382 @@ +/** + * @file lllazy.h + * @author Nat Goodspeed + * @date 2009-01-22 + * @brief Lazy instantiation of specified type. Useful in conjunction with + * Michael Feathers's "Extract and Override Getter" ("Working + * Effectively with Legacy Code", p. 352). + * + * Quoting his synopsis of steps on p.355: + * + * 1. Identify the object you need a getter for. + * 2. Extract all of the logic needed to create the object into a getter. + * 3. Replace all uses of the object with calls to the getter, and initialize + * the reference that holds the object to null in all constructors. + * 4. Add the first-time logic to the getter so that the object is constructed + * and assigned to the reference whenever the reference is null. + * 5. Subclass the class and override the getter to provide an alternative + * object for testing. + * + * It's the second half of bullet 3 (3b, as it were) that bothers me. I find + * it all too easy to imagine adding pointer initializers to all but one + * constructor... the one not exercised by my tests. That suggested using + * (e.g.) boost::scoped_ptr so you don't have to worry about + * destroying it either. + * + * However, introducing additional machinery allows us to encapsulate bullet 4 + * as well. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLLAZY_H) +#define LL_LLLAZY_H + +#include +#include +#include +#include + +/// LLLazyCommon simply factors out of LLLazy things that don't depend on +/// its template parameter. +class LLLazyCommon +{ +public: + /** + * This exception is thrown if you try to replace an LLLazy's factory + * (or T* instance) after it already has an instance in hand. Since T + * might well be stateful, we can't know the effect of silently discarding + * and replacing an existing instance, so we disallow it. This facility is + * intended for testing, and in a test scenario we can definitely control + * that. + */ + struct InstanceChange: public std::runtime_error + { + InstanceChange(const std::string& what): std::runtime_error(what) {} + }; + +protected: + /** + * InstanceChange might be appropriate in a couple of different LLLazy + * methods. Factor out the common logic. + */ + template + static void ensureNoInstance(const PTR& ptr) + { + if (ptr) + { + // Too late: we've already instantiated the lazy object. We don't + // know whether it's stateful or not, so it's not safe to discard + // the existing instance in favor of a replacement. + throw InstanceChange("Too late to replace LLLazy instance"); + } + } +}; + +/** + * LLLazy is useful when you have an outer class Outer that you're trying + * to bring under unit test, that contains a data member difficult to + * instantiate in a test harness. Typically the data member's class Inner has + * many thorny dependencies. Feathers generally advocates "Extract and + * Override Factory Method" (p. 350). But in C++, you can't call a derived + * class override of a virtual method from the derived class constructor, + * which limits applicability of "Extract and Override Factory Method." For + * such cases Feathers presents "Extract and Override Getter" (p. 352). + * + * So we'll assume that your class Outer contains a member like this: + * @code + * Inner mInner; + * @endcode + * + * LLLazy can be used to replace this member. You can directly declare: + * @code + * LLLazy mInner; + * @endcode + * and change references to mInner accordingly. + * + * (Alternatively, you can add a base class of the form + * LLLazyBase. This is discussed further in the LLLazyBase + * documentation.) + * + * LLLazy binds a boost::scoped_ptr and a factory functor + * returning T*. You can either bind that functor explicitly or let it default + * to the expression new T(). + * + * As long as LLLazy remains unreferenced, its T remains uninstantiated. + * The first time you use get(), operator*() or operator->() + * it will instantiate its T and thereafter behave like a pointer to it. + * + * Thus, any existing reference to mInner.member should be replaced + * with mInner->member. Any simple reference to @c mInner should be + * replaced by *mInner. + * + * (If the original declaration was a pointer initialized in Outer's + * constructor, e.g. Inner* mInner, so much the better. In that case + * you should be able to drop in LLLazy without much change.) + * + * The support for "Extract and Override Getter" lies in the fact that you can + * replace the factory functor -- or provide an explicit T*. Presumably this + * is most useful from a test subclass -- which suggests that your @c mInner + * member should be @c protected. + * + * Note that boost::lambda::new_ptr() makes a dandy factory + * functor, for either the set() method or LLLazy's constructor. If your T + * requires constructor arguments, use an expression more like + * boost::lambda::bind(boost::lambda::new_ptr(), arg1, arg2, ...). + * + * Of course the point of replacing the functor is to substitute a class that, + * though referenced as Inner*, is not an Inner; presumably this is a testing + * subclass of Inner (e.g. TestInner). Thus your test subclass TestOuter for + * the containing class Outer will contain something like this: + * @code + * class TestOuter: public Outer + * { + * public: + * TestOuter() + * { + * // mInner must be 'protected' rather than 'private' + * mInner.set(boost::lambda::new_ptr()); + * } + * ... + * }; + * @endcode + */ +template +class LLLazy: public LLLazyCommon +{ +public: + /// Any nullary functor returning T* will work as a Factory + typedef boost::function Factory; + + /// The default LLLazy constructor uses new T() as its Factory + LLLazy(): + mFactory(boost::lambda::new_ptr()) + {} + + /// Bind an explicit Factory functor + LLLazy(const Factory& factory): + mFactory(factory) + {} + + /// Reference T, instantiating it if this is the first access + const T& get() const + { + if (! mInstance) + { + // use the bound Factory functor + mInstance.reset(mFactory()); + } + return *mInstance; + } + + /// non-const get() + T& get() + { + return const_cast(const_cast*>(this)->get()); + } + + /// operator*() is equivalent to get() + const T& operator*() const { return get(); } + /// operator*() is equivalent to get() + T& operator*() { return get(); } + + /** + * operator->() must return (something resembling) T*. It's tempting to + * return the underlying boost::scoped_ptr, but that would require + * breaking out the lazy-instantiation logic from get() into a common + * private method. Assume the pointer used for operator->() access is very + * short-lived. + */ + const T* operator->() const { return &get(); } + /// non-const operator->() + T* operator->() { return &get(); } + + /// set(Factory). This will throw InstanceChange if mInstance has already + /// been set. + void set(const Factory& factory) + { + ensureNoInstance(mInstance); + mFactory = factory; + } + + /// set(T*). This will throw InstanceChange if mInstance has already been + /// set. + void set(T* instance) + { + ensureNoInstance(mInstance); + mInstance.reset(instance); + } + +private: + Factory mFactory; + // Consider an LLLazy member of a class we're accessing by const + // reference. We want to allow even const methods to touch the LLLazy + // member. Hence the actual pointer must be mutable because such access + // might assign it. + mutable boost::scoped_ptr mInstance; +}; + +#if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) +// Not gcc at all, or a gcc more recent than gcc 3.3 +#define GCC33 0 +#else +#define GCC33 1 +#endif + +/** + * LLLazyBase wraps LLLazy, giving you an alternative way to replace + * Inner mInner;. Instead of coding LLLazy mInner, + * you can add LLLazyBase to your Outer class's bases, e.g.: + * @code + * class Outer: public LLLazyBase + * { + * ... + * }; + * @endcode + * + * This gives you @c public get() and @c protected set() methods without + * having to make your LLLazy member @c protected. The tradeoff is that + * you must access the wrapped LLLazy using get() and set() rather than + * with operator*() or operator->(). + * + * This mechanism can be used for more than one member, but only if they're of + * different types. That is, you can replace: + * @code + * DifficultClass mDifficult; + * AwkwardType mAwkward; + * @endcode + * with: + * @code + * class Outer: public LLLazyBase, public LLLazyBase + * { + * ... + * }; + * @endcode + * but for a situation like this: + * @code + * DifficultClass mMainDifficult, mAuxDifficult; + * @endcode + * you should directly embed LLLazy (q.v.). + * + * For multiple LLLazyBase bases, e.g. the LLLazyBase, + * LLLazyBase example above, access the relevant get()/set() + * as (e.g.) LLLazyBase::get(). (This is why you + * can't have multiple LLLazyBase of the same T.) For a bit of syntactic + * sugar, please see getLazy()/setLazy(). + */ +template +class LLLazyBase +{ +public: + /// invoke default LLLazy constructor + LLLazyBase() {} + /// make wrapped LLLazy bind an explicit Factory + LLLazyBase(const typename LLLazy::Factory& factory): + mInstance(factory) + {} + + /// access to LLLazy::get() + T& get() { return *mInstance; } + /// access to LLLazy::get() + const T& get() const { return *mInstance; } + +protected: + // see getLazy()/setLazy() + #if (! GCC33) + template friend T2& getLazy(MYCLASS* this_); + template friend const T2& getLazy(const MYCLASS* this_); + #else // gcc 3.3 + template friend T2& getLazy(const MYCLASS* this_); + #endif // gcc 3.3 + template friend void setLazy(MYCLASS* this_, T2* instance); + template + friend void setLazy(MYCLASS* this_, const typename LLLazy::Factory& factory); + + /// access to LLLazy::set(Factory) + void set(const typename LLLazy::Factory& factory) + { + mInstance.set(factory); + } + + /// access to LLLazy::set(T*) + void set(T* instance) + { + mInstance.set(instance); + } + +private: + LLLazy mInstance; +}; + +/** + * @name getLazy()/setLazy() + * Suppose you have something like the following: + * @code + * class Outer: public LLLazyBase, public LLLazyBase + * { + * ... + * }; + * @endcode + * + * Your methods can reference the @c DifficultClass instance using + * LLLazyBase::get(), which is admittedly a bit ugly. + * Alternatively, you can write getLazy(this), which + * is somewhat more straightforward to read. + * + * Similarly, + * @code + * LLLazyBase::set(new TestDifficultClass()); + * @endcode + * could instead be written: + * @code + * setLazy(this, new TestDifficultClass()); + * @endcode + * + * @note + * I wanted to provide getLazy() and setLazy() without explicitly passing @c + * this. That would imply making them methods on a base class rather than free + * functions. But if LLLazyBase derives normally from (say) @c + * LLLazyGrandBase providing those methods, then unqualified getLazy() would + * be ambiguous: you'd have to write LLLazyBase::getLazy(), + * which is even uglier than LLLazyBase::get(), and therefore + * pointless. You can make the compiler not care which @c LLLazyGrandBase + * instance you're talking about by making @c LLLazyGrandBase a @c virtual + * base class of @c LLLazyBase. But in that case, + * LLLazyGrandBase::getLazy() can't access + * LLLazyBase::get()! + * + * We want getLazy() to access LLLazyBase::get() as if + * in the lexical context of some subclass method. Ironically, free functions + * let us do that better than methods on a @c virtual base class -- but that + * implies passing @c this explicitly. So be it. + */ +//@{ +#if (! GCC33) +template +T& getLazy(MYCLASS* this_) { return this_->LLLazyBase::get(); } +template +const T& getLazy(const MYCLASS* this_) { return this_->LLLazyBase::get(); } +#else // gcc 3.3 +// For const-correctness, we really should have two getLazy() variants: one +// accepting const MYCLASS* and returning const T&, the other accepting +// non-const MYCLASS* and returning non-const T&. This works fine on the Mac +// (gcc 4.0.1) and Windows (MSVC 8.0), but fails on our Linux 32-bit Debian +// Sarge stations (gcc 3.3.5). Since I really don't know how to beat that aging +// compiler over the head to make it do the right thing, I'm going to have to +// move forward with the wrong thing: a single getLazy() function that accepts +// const MYCLASS* and returns non-const T&. +template +T& getLazy(const MYCLASS* this_) { return const_cast(this_)->LLLazyBase::get(); } +#endif // gcc 3.3 +template +void setLazy(MYCLASS* this_, T* instance) { this_->LLLazyBase::set(instance); } +template +void setLazy(MYCLASS* this_, const typename LLLazy::Factory& factory) +{ + this_->LLLazyBase::set(factory); +} +//@} + +#endif /* ! defined(LL_LLLAZY_H) */ diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h new file mode 100644 index 0000000000..1b2958020f --- /dev/null +++ b/indra/llcommon/stringize.h @@ -0,0 +1,75 @@ +/** + * @file stringize.h + * @author Nat Goodspeed + * @date 2008-12-17 + * @brief stringize(item) template function and STRINGIZE(expression) macro + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_STRINGIZE_H) +#define LL_STRINGIZE_H + +#include + +/** + * stringize(item) encapsulates an idiom we use constantly, using + * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() + * to render a string expressing some item. + */ +template +std::string stringize(const T& item) +{ + std::ostringstream out; + out << item; + return out.str(); +} + +/** + * STRINGIZE(item1 << item2 << item3 ...) effectively expands to the + * following: + * @code + * std::ostringstream out; + * out << item1 << item2 << item3 ... ; + * return out.str(); + * @endcode + */ +#define STRINGIZE(EXPRESSION) (static_cast(Stringize() << EXPRESSION).str()) + +/** + * Helper class for STRINGIZE() macro. Ideally the body of + * STRINGIZE(EXPRESSION) would look something like this: + * @code + * (std::ostringstream() << EXPRESSION).str() + * @endcode + * That doesn't work because each of the relevant operator<<() functions + * accepts a non-const std::ostream&, to which you can't pass a temp instance + * of std::ostringstream. Stringize plays the necessary const tricks to make + * the whole thing work. + */ +class Stringize +{ +public: + /** + * This is the essence of Stringize. The leftmost << operator (the one + * coded in the STRINGIZE() macro) engages this operator<<() const method + * on the temp Stringize instance. Every other << operator (ones embedded + * in EXPRESSION) simply sees the std::ostream& returned by the first one. + * + * Finally, the STRINGIZE() macro downcasts that std::ostream& to + * std::ostringstream&. + */ + template + std::ostream& operator<<(const T& item) const + { + mOut << item; + return mOut; + } + +private: + mutable std::ostringstream mOut; +}; + +#endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp new file mode 100644 index 0000000000..db581d650f --- /dev/null +++ b/indra/llcommon/tests/lllazy_test.cpp @@ -0,0 +1,227 @@ +/** + * @file lllazy_test.cpp + * @author Nat Goodspeed + * @date 2009-01-28 + * @brief Tests of lllazy.h. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lllazy.h" +// STL headers +#include +// std headers +// external library headers +#include +#include +// other Linden headers +#include "../test/lltut.h" + +namespace bll = boost::lambda; + +/***************************************************************************** +* Test classes +*****************************************************************************/ + +// Let's say that because of its many external dependencies, YuckyFoo is very +// hard to instantiate in a test harness. +class YuckyFoo +{ +public: + virtual ~YuckyFoo() {} + virtual std::string whoami() const { return "YuckyFoo"; } +}; + +// Let's further suppose that YuckyBar is another hard-to-instantiate class. +class YuckyBar +{ +public: + YuckyBar(const std::string& which): + mWhich(which) + {} + virtual ~YuckyBar() {} + + virtual std::string identity() const { return std::string("YuckyBar(") + mWhich + ")"; } + +private: + const std::string mWhich; +}; + +// Pretend that this class would be tough to test because, up until we started +// trying to test it, it contained instances of both YuckyFoo and YuckyBar. +// Now we've refactored so it contains LLLazy and LLLazy. +// More than that, it contains them by virtue of deriving from +// LLLazyBase and LLLazyBase. +// We postulate two different LLLazyBases because, with only one, you need not +// specify *which* get()/set() method you're talking about. That's a simpler +// case. +class NeedsTesting: public LLLazyBase, public LLLazyBase +{ +public: + NeedsTesting(): + // mYuckyBar("RealYuckyBar") + LLLazyBase(bll::bind(bll::new_ptr(), "RealYuckyBar")) + {} + virtual ~NeedsTesting() {} + + virtual std::string describe() const + { + return std::string("NeedsTesting(") + getLazy(this).whoami() + ", " + + getLazy(this).identity() + ")"; + } + +private: + // These instance members were moved to LLLazyBases: + // YuckyFoo mYuckyFoo; + // YuckyBar mYuckyBar; +}; + +// Fake up a test YuckyFoo class +class TestFoo: public YuckyFoo +{ +public: + virtual std::string whoami() const { return "TestFoo"; } +}; + +// and a test YuckyBar +class TestBar: public YuckyBar +{ +public: + TestBar(const std::string& which): YuckyBar(which) {} + virtual std::string identity() const + { + return std::string("TestBar(") + YuckyBar::identity() + ")"; + } +}; + +// So here's a test subclass of NeedsTesting that uses TestFoo and TestBar +// instead of YuckyFoo and YuckyBar. +class TestNeedsTesting: public NeedsTesting +{ +public: + TestNeedsTesting() + { + // Exercise setLazy(T*) + setLazy(this, new TestFoo()); + // Exercise setLazy(Factory) + setLazy(this, bll::bind(bll::new_ptr(), "TestYuckyBar")); + } + + virtual std::string describe() const + { + return std::string("TestNeedsTesting(") + NeedsTesting::describe() + ")"; + } + + void toolate() + { + setLazy(this, new TestFoo()); + } +}; + +// This class tests having an explicit LLLazy instance as a named member, +// rather than deriving from LLLazyBase. +class LazyMember +{ +public: + YuckyFoo& getYuckyFoo() { return *mYuckyFoo; } + std::string whoisit() const { return mYuckyFoo->whoami(); } + +protected: + LLLazy mYuckyFoo; +}; + +// This is a test subclass of the above, dynamically replacing the +// LLLazy member. +class TestLazyMember: public LazyMember +{ +public: + // use factory setter + TestLazyMember() + { + mYuckyFoo.set(bll::new_ptr()); + } + + // use instance setter + TestLazyMember(YuckyFoo* instance) + { + mYuckyFoo.set(instance); + } +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lllazy_data + { + }; + typedef test_group lllazy_group; + typedef lllazy_group::object lllazy_object; + lllazy_group lllazygrp("lllazy"); + + template<> template<> + void lllazy_object::test<1>() + { + // Instantiate an official one, just because we can + NeedsTesting nt; + // and a test one + TestNeedsTesting tnt; +// std::cout << nt.describe() << '\n'; + ensure_equals(nt.describe(), "NeedsTesting(YuckyFoo, YuckyBar(RealYuckyBar))"); +// std::cout << tnt.describe() << '\n'; + ensure_equals(tnt.describe(), + "TestNeedsTesting(NeedsTesting(TestFoo, TestBar(YuckyBar(TestYuckyBar))))"); + } + + template<> template<> + void lllazy_object::test<2>() + { + TestNeedsTesting tnt; + std::string threw; + try + { + tnt.toolate(); + } + catch (const LLLazyCommon::InstanceChange& e) + { + threw = e.what(); + } + ensure_contains("InstanceChange exception", threw, "replace LLLazy instance"); + } + + template<> template<> + void lllazy_object::test<3>() + { + { + LazyMember lm; + // operator*() on-demand instantiation + ensure_equals(lm.getYuckyFoo().whoami(), "YuckyFoo"); + } + { + LazyMember lm; + // operator->() on-demand instantiation + ensure_equals(lm.whoisit(), "YuckyFoo"); + } + } + + template<> template<> + void lllazy_object::test<4>() + { + { + // factory setter + TestLazyMember tlm; + ensure_equals(tlm.whoisit(), "TestFoo"); + } + { + // instance setter + TestLazyMember tlm(new TestFoo()); + ensure_equals(tlm.whoisit(), "TestFoo"); + } + } +} // namespace tut diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 0f3e159802..c0f7a4d335 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -3,6 +3,7 @@ project(llmessage) include(00-Common) +include(LLAddBuildTest) include(LLCommon) include(LLMath) include(LLMessage) @@ -63,6 +64,7 @@ set(llmessage_SOURCE_FILES llregionpresenceverifier.cpp llsdappservices.cpp llsdhttpserver.cpp + llsdmessage.cpp llsdmessagebuilder.cpp llsdmessagereader.cpp llsdrpcclient.cpp @@ -156,6 +158,7 @@ set(llmessage_HEADER_FILES llregionpresenceverifier.h llsdappservices.h llsdhttpserver.h + llsdmessage.h llsdmessagebuilder.h llsdmessagereader.h llsdrpcclient.h @@ -217,5 +220,6 @@ IF (NOT LINUX AND VIEWER) #ADD_BUILD_TEST(llhttpclientadapter llmessage) ADD_BUILD_TEST(lltrustedmessageservice llmessage) ADD_BUILD_TEST(lltemplatemessagedispatcher llmessage) + # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! + ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") ENDIF (NOT LINUX AND VIEWER) - diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index 629bd3836d..43abc2953d 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -96,7 +96,7 @@ public: { } - boost::signals::connection setCallback(const LLCacheNameCallback& cb) + boost::signals2::connection setCallback(const LLCacheNameCallback& cb) { return mSignal.connect(cb); } @@ -215,7 +215,7 @@ public: Impl(LLMessageSystem* msg); ~Impl(); - boost::signals::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback); + boost::signals2::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback); void addPending(const LLUUID& id, const LLHost& host); void processPendingAsks(); @@ -276,10 +276,10 @@ LLCacheName::Impl::~Impl() for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer()); } -boost::signals::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback) { PendingReply* reply = new PendingReply(id, LLHost()); - boost::signals::connection res = reply->setCallback(callback); + boost::signals2::connection res = reply->setCallback(callback); mReplyQueue.push_back(reply); return res; } @@ -295,7 +295,7 @@ void LLCacheName::setUpstream(const LLHost& upstream_host) impl.mUpstreamHost = upstream_host; } -boost::signals::connection LLCacheName::addObserver(const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback& callback) { return impl.mSignal.connect(callback); } @@ -554,9 +554,9 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) // we call it immediately. -Steve // NOTE: Even though passing first and last name is a bit of extra overhead, it eliminates the // potential need for any parsing should any code need to handle first and last name independently. -boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) { - boost::signals::connection res; + boost::signals2::connection res; if(id.isNull()) { @@ -600,7 +600,7 @@ boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, con return res; } -boost::signals::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) +boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) { return get(id, is_group, boost::bind(callback, _1, _2, _3, _4, user_data)); } diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h index 414b6590f6..792f1aeb0a 100644 --- a/indra/llmessage/llcachename.h +++ b/indra/llmessage/llcachename.h @@ -34,17 +34,17 @@ #define LL_LLCACHENAME_H #include -#include +#include class LLMessageSystem; class LLHost; class LLUUID; -typedef boost::signal LLCacheNameSignal; +typedef boost::signals2::signal LLCacheNameSignal; typedef LLCacheNameSignal::slot_type LLCacheNameCallback; // Old callback with user data for compatability @@ -69,7 +69,7 @@ public: // for simulators, this is the data server void setUpstream(const LLHost& upstream_host); - boost::signals::connection addObserver(const LLCacheNameCallback& callback); + boost::signals2::connection addObserver(const LLCacheNameCallback& callback); // janky old format. Remove after a while. Phoenix. 2008-01-30 void importFile(LLFILE* fp); @@ -96,10 +96,10 @@ public: // If the data is currently available, may call the callback immediatly // otherwise, will request the data, and will call the callback when // available. There is no garuntee the callback will ever be called. - boost::signals::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); + boost::signals2::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); // LEGACY - boost::signals::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); + boost::signals2::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); // This method needs to be called from time to time to send out // requests. diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp new file mode 100644 index 0000000000..f663268466 --- /dev/null +++ b/indra/llmessage/llsdmessage.cpp @@ -0,0 +1,150 @@ +/** + * @file llsdmessage.cpp + * @author Nat Goodspeed + * @date 2008-10-31 + * @brief Implementation for llsdmessage. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llsdserialize.h" +#include "llhttpclient.h" +#include "llmessageconfig.h" +#include "llhost.h" +#include "message.h" +#include "llsdutil.h" + +// Declare a static LLSDMessage instance to ensure that we have a listener as +// soon as someone tries to post on our canonical LLEventPump name. +static LLSDMessage httpListener; + +LLSDMessage::LLSDMessage(): + // Instantiating our own local LLEventPump with a string name the + // constructor is NOT allowed to tweak is a way of ensuring Singleton + // semantics: attempting to instantiate a second LLSDMessage object would + // throw LLEventPump::DupPumpName. + mEventPump("LLHTTPClient") +{ + mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1)); +} + +bool LLSDMessage::httpListener(const LLSD& request) +{ + // Extract what we want from the request object. We do it all up front + // partly to document what we expect. + LLSD::String url(request["url"]); + LLSD payload(request["payload"]); + LLSD::String reply(request["reply"]); + LLSD::String error(request["error"]); + LLSD::Real timeout(request["timeout"]); + // If the LLSD doesn't even have a "url" key, we doubt it was intended for + // this listener. + if (url.empty()) + { + std::ostringstream out; + out << "request event without 'url' key to '" << mEventPump.getName() << "'"; + throw ArgError(out.str()); + } + // Establish default timeout. This test relies on LLSD::asReal() returning + // exactly 0.0 for an undef value. + if (! timeout) + { + timeout = HTTP_REQUEST_EXPIRY_SECS; + } + LLHTTPClient::post(url, payload, + new LLSDMessage::EventResponder(LLEventPumps::instance(), + url, "POST", reply, error), + LLSD(), // headers + timeout); + return false; +} + +void LLSDMessage::EventResponder::result(const LLSD& data) +{ + // If our caller passed an empty replyPump name, they're not + // listening: this is a fire-and-forget message. Don't bother posting + // to the pump whose name is "". + if (! mReplyPump.empty()) + { + mPumps.obtain(mReplyPump).post(data); + } + else // default success handling + { + LL_INFOS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget << "' succeeded" + << LL_ENDL; + } +} + +void LLSDMessage::EventResponder::error(U32 status, const std::string& reason, const LLSD& content) +{ + // If our caller passed an empty errorPump name, they're not + // listening: "default error handling is acceptable." Only post to an + // explicit pump name. + if (! mErrorPump.empty()) + { + LLSD info; + info["target"] = mTarget; + info["message"] = mMessage; + info["status"] = LLSD::Integer(status); + info["reason"] = reason; + info["content"] = content; + mPumps.obtain(mErrorPump).post(info); + } + else // default error handling + { + // convention seems to be to use llinfos, but that seems a bit casual? + LL_WARNS("LLSDMessage::EventResponder") + << "'" << mMessage << "' to '" << mTarget + << "' failed with code " << status << ": " << reason << '\n' + << ll_pretty_print_sd(content) + << LL_ENDL; + } +} + +LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder, + const std::string& name): + mResponder(responder), + mReplyPump(name + ".reply", true), // tweak name for uniqueness + mErrorPump(name + ".error", true) +{ + mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true)); + mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false)); +} + +bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success) +{ + if (success) + { + mResponder->result(payload); + } + else + { + mResponder->error(payload["status"].asInteger(), payload["reason"], payload["content"]); + } + + /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/ + delete this; + // Destruction of mResponder will usually implicitly free its referent as well + /*------------------------- NOTHING AFTER THIS -------------------------*/ + return false; +} + +void LLSDMessage::link() +{ +} diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h new file mode 100644 index 0000000000..8ae9451243 --- /dev/null +++ b/indra/llmessage/llsdmessage.h @@ -0,0 +1,146 @@ +/** + * @file llsdmessage.h + * @author Nat Goodspeed + * @date 2008-10-30 + * @brief API intended to unify sending capability, UDP and TCP messages: + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSDMESSAGE_H) +#define LL_LLSDMESSAGE_H + +#include "llerror.h" // LOG_CLASS() +#include "llevents.h" // LLEventPumps +#include "llhttpclient.h" +#include +#include + +class LLSD; + +/** + * Class managing the messaging API described in + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + */ +class LLSDMessage +{ + LOG_CLASS(LLSDMessage); + +public: + LLSDMessage(); + + /// Exception if you specify arguments badly + struct ArgError: public std::runtime_error + { + ArgError(const std::string& what): + std::runtime_error(std::string("ArgError: ") + what) {} + }; + + /** + * The response idiom used by LLSDMessage -- LLEventPump names on which to + * post reply or error -- is designed for the case in which your + * reply/error handlers are methods on the same class as the method + * sending the message. Any state available to the sending method that + * must be visible to the reply/error methods can conveniently be stored + * on that class itself, if it's not already. + * + * The LLHTTPClient::Responder idiom requires a separate instance of a + * separate class so that it can dispatch to the code of interest by + * calling canonical virtual methods. Interesting state must be copied + * into that new object. + * + * With some trepidation, because existing response code is packaged in + * LLHTTPClient::Responder subclasses, we provide this adapter class + * for transitional purposes only. Instantiate a new heap + * ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass + * ResponderAdapter::getReplyName() and/or getErrorName() in your + * LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The + * ResponderAdapter will call the appropriate Responder method, then + * @c delete itself. + */ + class ResponderAdapter + { + public: + /** + * Bind the new LLHTTPClient::Responder subclass instance. + * + * Passing the constructor a name other than the default is only + * interesting if you suspect some usage will lead to an exception or + * log message. + */ + ResponderAdapter(LLHTTPClient::ResponderPtr responder, + const std::string& name="ResponderAdapter"); + + /// EventPump name on which LLSDMessage should post reply event + std::string getReplyName() const { return mReplyPump.getName(); } + /// EventPump name on which LLSDMessage should post error event + std::string getErrorName() const { return mErrorPump.getName(); } + + private: + // We have two different LLEventStreams, though we route them both to + // the same listener, so that we can bind an extra flag identifying + // which case (reply or error) reached that listener. + bool listener(const LLSD&, bool success); + + LLHTTPClient::ResponderPtr mResponder; + LLEventStream mReplyPump, mErrorPump; + }; + + /** + * Force our implementation file to be linked with caller. The .cpp file + * contains a static instance of this class, which must be linked into the + * executable to support the canonical listener. But since the primary + * interface to that static instance is via a named LLEventPump rather + * than by direct reference, the linker doesn't necessarily perceive the + * necessity to bring in the translation unit. Referencing this dummy + * method forces the issue. + */ + static void link(); + +private: + friend class LLCapabilityListener; + /// Responder used for internal purposes by LLSDMessage and + /// LLCapabilityListener. Others should use higher-level APIs. + class EventResponder: public LLHTTPClient::Responder + { + public: + /** + * LLHTTPClient::Responder that dispatches via named LLEventPump instances. + * We bind LLEventPumps, even though it's an LLSingleton, for testability. + * We bind the string names of the desired LLEventPump instances rather + * than actually obtain()ing them so we only obtain() the one we're going + * to use. If the caller doesn't bother to listen() on it, the other pump + * may never materialize at all. + * @a target and @a message are only to clarify error processing. + * For a capability message, @a target should be the region description, + * @a message should be the capability name. + * For a service with a visible URL, pass the URL as @a target and the HTTP verb + * (e.g. "POST") as @a message. + */ + EventResponder(LLEventPumps& pumps, + const std::string& target, const std::string& message, + const std::string& replyPump, const std::string& errorPump): + mPumps(pumps), + mTarget(target), + mMessage(message), + mReplyPump(replyPump), + mErrorPump(errorPump) + {} + + virtual void result(const LLSD& data); + virtual void error(U32 status, const std::string& reason, const LLSD& content); + + private: + LLEventPumps& mPumps; + const std::string mTarget, mMessage, mReplyPump, mErrorPump; + }; + +private: + bool httpListener(const LLSD&); + LLEventStream mEventPump; +}; + +#endif /* ! defined(LL_LLSDMESSAGE_H) */ diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp new file mode 100644 index 0000000000..2957d7cc4f --- /dev/null +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -0,0 +1,113 @@ +/** + * @file llsdmessage_tut.cpp + * @author Nat Goodspeed + * @date 2008-12-22 + * @brief Test of llsdmessage.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +#include +// std headers +#include +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsdserialize.h" +#include "llevents.h" +#include "stringize.h" +#include "llhost.h" +#include "tests/networkio.h" +#include "tests/commtest.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llsdmessage_data: public commtest_data + { + LLEventPump& httpPump; + + llsdmessage_data(): + httpPump(pumps.obtain("LLHTTPClient")) + { + LLSDMessage::link(); + } + }; + typedef test_group llsdmessage_group; + typedef llsdmessage_group::object llsdmessage_object; + llsdmessage_group llsdmgr("llsdmessage"); + + template<> template<> + void llsdmessage_object::test<1>() + { + bool threw = false; + // This should fail... + try + { + LLSDMessage localListener; + } + catch (const LLEventPump::DupPumpName&) + { + threw = true; + } + ensure("second LLSDMessage should throw", threw); + } + + template<> template<> + void llsdmessage_object::test<2>() + { + LLSD request, body; + body["data"] = "yes"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + bool threw = false; + try + { + httpPump.post(request); + } + catch (const LLSDMessage::ArgError&) + { + threw = true; + } + ensure("missing URL", threw); + } + + template<> template<> + void llsdmessage_object::test<3>() + { + LLSD request, body; + body["data"] = "yes"; + request["url"] = server + "got-message"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + httpPump.post(request); + ensure("got response", netio.pump()); + ensure("success response", success); + ensure_equals(result.asString(), "success"); + + body["status"] = 499; + body["reason"] = "custom error message"; + request["url"] = server + "fail"; + request["payload"] = body; + httpPump.post(request); + ensure("got response", netio.pump()); + ensure("failure response", ! success); + ensure_equals(result["status"].asInteger(), body["status"].asInteger()); + ensure_equals(result["reason"].asString(), body["reason"].asString()); + } +} // namespace tut diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 1f6cd6ddf9..cdd364797c 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -240,37 +240,37 @@ void LLButton::onCommit() LLUICtrl::onCommit(); } -boost::signals::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) { return mCommitSignal.connect(cb); } -boost::signals::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMouseDownSignal.connect(cb); } -boost::signals::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMouseUpSignal.connect(cb); } -boost::signals::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) { return mHeldDownSignal.connect(cb); } // *TODO: Deprecate (for backwards compatability only) -boost::signals::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) +boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) { return setClickedCallback(boost::bind(cb, data)); } -boost::signals::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) +boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) { return setMouseDownCallback(boost::bind(cb, data)); } -boost::signals::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) +boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) { return setMouseUpCallback(boost::bind(cb, data)); } -boost::signals::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) +boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) { return setHeldDownCallback(boost::bind(cb, data)); } diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index f146ef9dc2..99f4b94805 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -150,17 +150,17 @@ public: void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } - boost::signals::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button - boost::signals::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); // mouse up, EVEN IF NOT IN BUTTON + boost::signals2::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); // mouse up, EVEN IF NOT IN BUTTON // Passes a 'count' parameter in the commit param payload, i.e. param["count"]) - boost::signals::connection setHeldDownCallback( const commit_signal_t::slot_type& cb ); // Mouse button held down and in button + boost::signals2::connection setHeldDownCallback( const commit_signal_t::slot_type& cb ); // Mouse button held down and in button // *TODO: Deprecate (for backwards compatability only) - boost::signals::connection setClickedCallback( button_callback_t cb, void* data ); - boost::signals::connection setMouseDownCallback( button_callback_t cb, void* data ); - boost::signals::connection setMouseUpCallback( button_callback_t cb, void* data ); - boost::signals::connection setHeldDownCallback( button_callback_t cb, void* data ); + boost::signals2::connection setClickedCallback( button_callback_t cb, void* data ); + boost::signals2::connection setMouseDownCallback( button_callback_t cb, void* data ); + boost::signals2::connection setMouseUpCallback( button_callback_t cb, void* data ); + boost::signals2::connection setHeldDownCallback( button_callback_t cb, void* data ); void setHeldDownDelay( F32 seconds, S32 frames = 0) { mHeldDownDelay = seconds; mHeldDownFrameDelay = frames; } diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index a3588d9dae..fc2e6e163b 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -65,6 +65,8 @@ #include #include +using namespace LLOldEvents; + // static LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 3cb76efce0..ffaecc2c15 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -285,12 +285,12 @@ public: //virtual void draw(); - boost::signals::connection setClickCallback( const commit_signal_t::slot_type& cb ) + boost::signals2::connection setClickCallback( const commit_signal_t::slot_type& cb ) { return setCommitCallback(cb); } - boost::signals::connection setEnableCallback( const enable_signal_t::slot_type& cb ) + boost::signals2::connection setEnableCallback( const enable_signal_t::slot_type& cb ) { return mEnableSignal.connect(cb); } @@ -335,7 +335,7 @@ public: // called to rebuild the draw label virtual void buildDrawLabel( void ); - boost::signals::connection setCheckCallback( const enable_signal_t::slot_type& cb ) + boost::signals2::connection setCheckCallback( const enable_signal_t::slot_type& cb ) { return mCheckSignal.connect(cb); } @@ -823,7 +823,7 @@ private: // *TODO: Eliminate // For backwards compatability only; generally just use boost::bind -class view_listener_t : public boost::signals::trackable +class view_listener_t : public boost::signals2::trackable { public: virtual bool handleEvent(const LLSD& userdata) = 0; diff --git a/indra/llui/llmultislider.h b/indra/llui/llmultislider.h index 9c01b528a7..89d44eaa87 100644 --- a/indra/llui/llmultislider.h +++ b/indra/llui/llmultislider.h @@ -78,8 +78,8 @@ public: /*virtual*/ void setValue(const LLSD& value); /*virtual*/ LLSD getValue() const { return mValue; } - boost::signals::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMouseDownSignal.connect(cb); } - boost::signals::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMouseUpSignal.connect(cb); } + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMouseDownSignal.connect(cb); } + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMouseUpSignal.connect(cb); } bool findUnusedValue(F32& initVal); const std::string& addSlider(); diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index 14584e6df5..bc981a9b57 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -460,12 +460,12 @@ void LLMultiSliderCtrl::setPrecision(S32 precision) updateText(); } -boost::signals::connection LLMultiSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMultiSlider->setMouseDownCallback( cb ); } -boost::signals::connection LLMultiSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMultiSlider->setMouseUpCallback( cb ); } diff --git a/indra/llui/llmultisliderctrl.h b/indra/llui/llmultisliderctrl.h index 85ba77b7df..4855ed4926 100644 --- a/indra/llui/llmultisliderctrl.h +++ b/indra/llui/llmultisliderctrl.h @@ -115,8 +115,8 @@ public: void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } - boost::signals::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); virtual void onTabInto(); diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 34ff21268e..569112aef1 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -685,7 +685,7 @@ std::string LLNotification::getURL() const // ========================================================= // LLNotificationChannel implementation // --- -void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot) { // when someone wants to connect to a channel, we first throw them // all of the notifications that are already in the channel @@ -693,23 +693,23 @@ void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type // only about new notifications for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) { - slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); + slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); } // and then connect the signal so that all future notifications will also be // forwarded. - mChanged.connect(slot); + return mChanged.connect(slot); } -void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) { // these two filters only fire for notifications added after the current one, because // they don't participate in the hierarchy. - mPassedFilter.connect(slot); + return mPassedFilter.connect(slot); } -void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) { - mFailedFilter.connect(slot); + return mFailedFilter.connect(slot); } // external call, conforms to our standard signature @@ -867,8 +867,7 @@ mParent(parent) else { LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); - LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1); - p->connectChanged(f); + p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); } } @@ -1065,11 +1064,11 @@ void LLNotifications::createDefaultChannels() // connect action methods to these channels LLNotifications::instance().getChannel("Expiration")-> - connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); + connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); LLNotifications::instance().getChannel("Unique")-> - connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); + connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); LLNotifications::instance().getChannel("Unique")-> - connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); LLNotifications::instance().getChannel("Ignore")-> connectFailedFilter(&handleIgnoredNotification); } diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index de86f5daa2..5c8d146e0c 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -91,13 +91,14 @@ #include #include -#include +#include #include // we want to minimize external dependencies, but this one is important #include "llsd.h" // and we need this to manage the notification callbacks +#include "llevents.h" #include "llfunctorregistry.h" #include "llui.h" #include "llmemory.h" @@ -105,36 +106,6 @@ class LLNotification; typedef boost::shared_ptr LLNotificationPtr; -/***************************************************************************** -* Signal and handler declarations -* Using a single handler signature means that we can have a common handler -* type, rather than needing a distinct one for each different handler. -*****************************************************************************/ - -/** - * A boost::signals Combiner that stops the first time a handler returns true - * We need this because we want to have our handlers return bool, so that - * we have the option to cause a handler to stop further processing. The - * default handler fails when the signal returns a value but has no slots. - */ -struct LLStopWhenHandled -{ - typedef bool result_type; - - template - result_type operator()(InputIterator first, InputIterator last) const - { - for (InputIterator si = first; si != last; ++si) - { - if (*si) - { - return true; - } - } - return false; - } -}; - typedef enum e_notification_priority { @@ -145,27 +116,11 @@ typedef enum e_notification_priority NOTIFICATION_PRIORITY_CRITICAL } ENotificationPriority; -/** - * We want to have a standard signature for all signals; this way, - * we can easily document a protocol for communicating across - * dlls and into scripting languages someday. - * we want to return a bool to indicate whether the signal has been - * handled and should NOT be passed on to other listeners. - * Return true to stop further handling of the signal, and false - * to continue. - * We take an LLSD because this way the contents of the signal - * are independent of the API used to communicate it. - * It is const ref because then there's low cost to pass it; - * if you only need to inspect it, it's very cheap. - */ - typedef boost::function LLNotificationResponder; typedef LLFunctorRegistry LLNotificationFunctorRegistry; typedef LLFunctorRegistration LLNotificationFunctorRegistration; -typedef boost::signal LLStandardSignal; - // context data that can be looked up via a notification's payload by the display logic // derive from this class to implement specific contexts class LLNotificationContext : public LLInstanceTracker @@ -713,7 +668,7 @@ typedef std::multimap LLNotificationMap; // all of the built-in tests should attach to the "Visible" channel // class LLNotificationChannelBase : - public boost::signals::trackable + public LLEventTrackable { LOG_CLASS(LLNotificationChannelBase); public: @@ -723,15 +678,45 @@ public: virtual ~LLNotificationChannelBase() {} // you can also connect to a Channel, so you can be notified of // changes to this channel - virtual void connectChanged(const LLStandardSignal::slot_type& slot); - virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot); - virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot); + template + LLBoundListener connectChanged(const LISTENER& slot) + { + // Examine slot to see if it binds an LLEventTrackable subclass, or a + // boost::shared_ptr to something, or a boost::weak_ptr to something. + // Call this->connectChangedImpl() to actually connect it. + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectChangedImpl, + this, + _1)); + } + template + LLBoundListener connectPassedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl, + this, + _1)); + } + template + LLBoundListener connectFailedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl, + this, + _1)); + } // use this when items change or to add a new one bool updateItem(const LLSD& payload); const LLNotificationFilter& getFilter() { return mFilter; } protected: + LLBoundListener connectChangedImpl(const LLEventListener& slot); + LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); + LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); + LLNotificationSet mItems; LLStandardSignal mChanged; LLStandardSignal mPassedFilter; diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h index 39c55afd8c..dad65fcce0 100644 --- a/indra/llui/llslider.h +++ b/indra/llui/llslider.h @@ -67,8 +67,8 @@ public: virtual void setMinValue(F32 min_value) { LLF32UICtrl::setMinValue(min_value); updateThumbRect(); } virtual void setMaxValue(F32 max_value) { LLF32UICtrl::setMaxValue(max_value); updateThumbRect(); } - boost::signals::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMouseDownSignal.connect(cb); } - boost::signals::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMouseUpSignal.connect(cb); } + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mMouseDownSignal.connect(cb); } + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mMouseUpSignal.connect(cb); } virtual BOOL handleHover(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index d5053478a6..2c8aed6196 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -375,12 +375,12 @@ void LLSliderCtrl::setPrecision(S32 precision) updateText(); } -boost::signals::connection LLSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) { return mSlider->setMouseDownCallback( cb ); } -boost::signals::connection LLSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) +boost::signals2::connection LLSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) { return mSlider->setMouseUpCallback( cb ); } diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h index 0bcb1ccc9b..5bdbbfcbcc 100644 --- a/indra/llui/llsliderctrl.h +++ b/indra/llui/llsliderctrl.h @@ -111,8 +111,8 @@ public: void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } - boost::signals::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); /*virtual*/ void onTabInto(); diff --git a/indra/llui/llui.h b/indra/llui/llui.h index 18aa1aa143..71396e10d9 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -45,7 +45,7 @@ #include "lluiimage.h" // *TODO: break this dependency, need to add #include "lluiimage.h" to all widgets that hold an Optional in their paramblocks #include "llinitparam.h" #include "llregistry.h" -#include +#include #include "lllazyvalue.h" // LLUIFactory @@ -576,7 +576,7 @@ public: class LLCallbackRegistry { public: - typedef boost::signal callback_signal_t; + typedef boost::signals2::signal callback_signal_t; void registerCallback(const callback_signal_t::slot_type& slot) { diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index da0db0424a..99811809a8 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -749,11 +749,11 @@ LLUICtrl* LLUICtrl::getParentUICtrl() const } // *TODO: Deprecate; for backwards compatability only: -boost::signals::connection LLUICtrl::setCommitCallback( boost::function cb, void* data) +boost::signals2::connection LLUICtrl::setCommitCallback( boost::function cb, void* data) { return setCommitCallback( boost::bind(cb, _1, data)); } -boost::signals::connection LLUICtrl::setValidateBeforeCommit( boost::function cb ) +boost::signals2::connection LLUICtrl::setValidateBeforeCommit( boost::function cb ) { return mValidateSignal.connect(boost::bind(cb, _2)); } diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index e82102d531..6d310dca22 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -74,16 +74,16 @@ protected: }; class LLUICtrl - : public LLView, public LLFocusableElement, public boost::signals::trackable + : public LLView, public LLFocusableElement, public boost::signals2::trackable { public: typedef boost::function commit_callback_t; - typedef boost::signal commit_signal_t; + typedef boost::signals2::signal commit_signal_t; typedef boost::function enable_callback_t; - typedef boost::signal enable_signal_t; + typedef boost::signals2::signal enable_signal_t; struct CallbackParam : public LLInitParam::Block { @@ -217,12 +217,12 @@ public: LLUICtrl* getParentUICtrl() const; - boost::signals::connection setCommitCallback( const commit_signal_t::slot_type& cb ) { return mCommitSignal.connect(cb); } - boost::signals::connection setValidateCallback( const enable_signal_t::slot_type& cb ) { return mValidateSignal.connect(cb); } + boost::signals2::connection setCommitCallback( const commit_signal_t::slot_type& cb ) { return mCommitSignal.connect(cb); } + boost::signals2::connection setValidateCallback( const enable_signal_t::slot_type& cb ) { return mValidateSignal.connect(cb); } // *TODO: Deprecate; for backwards compatability only: - boost::signals::connection setCommitCallback( boost::function cb, void* data); - boost::signals::connection setValidateBeforeCommit( boost::function cb ); + boost::signals2::connection setCommitCallback( boost::function cb, void* data); + boost::signals2::connection setValidateBeforeCommit( boost::function cb ); LLUICtrl* findRootMostFocusRoot(); @@ -250,9 +250,9 @@ protected: LLViewModelPtr mViewModel; LLControlVariable* mControlVariable; - boost::signals::connection mControlConnection; + boost::signals2::connection mControlConnection; LLControlVariable* mEnabledControlVariable; - boost::signals::connection mEnabledControlConnection; + boost::signals2::connection mEnabledControlConnection; private: diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 536d0c23f8..0a28075ed6 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -56,6 +56,8 @@ #include "lltexteditor.h" #include "lltextbox.h" +using namespace LLOldEvents; + BOOL LLView::sDebugRects = FALSE; BOOL LLView::sDebugKeys = FALSE; S32 LLView::sDepth = 0; diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index b1ac85812c..3f7714f505 100644 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -42,6 +42,5 @@ add_library (llxml ${llxml_SOURCE_FILES}) target_link_libraries( llxml llvfs llmath - ${BOOST_SIGNALS_LIBRARY} ${EXPAT_LIBRARIES} ) diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index 1782c20a7e..0a8e665c55 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -56,7 +56,7 @@ #endif #include -#include +#include #if LL_WINDOWS # if (_MSC_VER >= 1300 && _MSC_VER < 1400) @@ -92,8 +92,8 @@ class LLControlVariable : public LLRefCount, boost::noncopyable friend class LLControlGroup; public: - typedef boost::signal validate_signal_t; - typedef boost::signal commit_signal_t; + typedef boost::signals2::signal validate_signal_t; + typedef boost::signals2::signal commit_signal_t; private: std::string mName; @@ -378,7 +378,7 @@ private: private: T mCachedValue; eControlType mType; - boost::signals::connection mConnection; + boost::signals2::connection mConnection; }; template diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 9533281688..e1f545adb5 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -80,6 +80,7 @@ set(viewer_SOURCE_FILES llbox.cpp llcallbacklist.cpp llcallingcard.cpp + llcapabilitylistener.cpp llcaphttpsender.cpp llchatbar.cpp llclassifiedinfo.cpp @@ -474,6 +475,8 @@ set(viewer_HEADER_FILES llbox.h llcallbacklist.h llcallingcard.h + llcapabilitylistener.h + llcapabilityprovider.h llcaphttpsender.h llchatbar.h llclassifiedinfo.h @@ -1385,3 +1388,5 @@ if (INSTALL) endif (INSTALL) ADD_VIEWER_BUILD_TEST(llagentaccess viewer) +ADD_VIEWER_COMM_BUILD_TEST(llcapabilitylistener viewer + ${CMAKE_CURRENT_SOURCE_DIR}/../llmessage/tests/test_llsdmessage_peer.py) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 263c2b52bf..f97f9f607f 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -136,6 +136,8 @@ #include "llviewerjoystick.h" #include "llfollowcam.h" #include "lltrans.h" +#include "stringize.h" +#include "llcapabilitylistener.h" #include "llnavigationbar.h" //to show/hide navigation bar when changing mouse look state @@ -947,7 +949,7 @@ LLViewerRegion *LLAgent::getRegion() const } -const LLHost& LLAgent::getRegionHost() const +LLHost LLAgent::getRegionHost() const { if (mRegionp) { @@ -4699,109 +4701,128 @@ void LLAgent::lookAtLastChat() const F32 SIT_POINT_EXTENTS = 0.2f; +LLSD ll_sdmap_from_vector3(const LLVector3& vec) +{ + LLSD ret; + ret["X"] = vec.mV[VX]; + ret["Y"] = vec.mV[VY]; + ret["Z"] = vec.mV[VZ]; + return ret; +} + +LLVector3 ll_vector3_from_sdmap(const LLSD& sd) +{ + LLVector3 ret; + ret.mV[VX] = F32(sd["X"].asReal()); + ret.mV[VY] = F32(sd["Y"].asReal()); + ret.mV[VZ] = F32(sd["Z"].asReal()); + return ret; +} + void LLAgent::setStartPosition( U32 location_id ) { - LLViewerObject *object; + LLViewerObject *object; - if ( !(gAgentID == LLUUID::null) ) - { + if (gAgentID == LLUUID::null) + { + return; + } // we've got an ID for an agent viewerobject object = gObjectList.findObject(gAgentID); - if (object) + if (! object) { - // we've got the viewer object - // Sometimes the agent can be velocity interpolated off of - // this simulator. Clamp it to the region the agent is - // in, a little bit in on each side. - const F32 INSET = 0.5f; //meters - const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); - - LLVector3 agent_pos = getPositionAgent(); - LLVector3 agent_look_at = mFrameAgent.getAtAxis(); - - if (mAvatarObject.notNull()) - { - // the z height is at the agent's feet - agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ]; - } - - agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); - agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); - - // Don't let them go below ground, or too high. - agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], - mRegionp->getLandHeightRegion( agent_pos ), - LLWorld::getInstance()->getRegionMaxHeight() ); - // Send the CapReq - - LLSD body; - - std::string url = gAgent.getRegion()->getCapability("HomeLocation"); - std::ostringstream strBuffer; - if( url.empty() ) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetStartLocationRequest); - msg->nextBlockFast( _PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->nextBlockFast( _PREHASH_StartLocationData); - // corrected by sim - msg->addStringFast(_PREHASH_SimName, ""); - msg->addU32Fast(_PREHASH_LocationID, location_id); - msg->addVector3Fast(_PREHASH_LocationPos, agent_pos); - msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis()); - - // Reliable only helps when setting home location. Last - // location is sent on quit, and we don't have time to ack - // the packets. - msg->sendReliable(mRegionp->getHost()); - - const U32 HOME_INDEX = 1; - if( HOME_INDEX == location_id ) - { - setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); - } - } - else - { - strBuffer << location_id; - body["HomeLocation"]["LocationId"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_pos.mV[VX]; - body["HomeLocation"]["LocationPos"]["X"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_pos.mV[VY]; - body["HomeLocation"]["LocationPos"]["Y"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_pos.mV[VZ]; - body["HomeLocation"]["LocationPos"]["Z"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_look_at.mV[VX]; - body["HomeLocation"]["LocationLookAt"]["X"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_look_at.mV[VY]; - body["HomeLocation"]["LocationLookAt"]["Y"] = strBuffer.str(); - - strBuffer.str(""); - strBuffer << agent_look_at.mV[VZ]; - body["HomeLocation"]["LocationLookAt"]["Z"] = strBuffer.str(); - - LLHTTPClient::post( url, body, new LLHomeLocationResponder() ); - } + llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; + return; + } + // we've got the viewer object + // Sometimes the agent can be velocity interpolated off of + // this simulator. Clamp it to the region the agent is + // in, a little bit in on each side. + const F32 INSET = 0.5f; //meters + const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); + + LLVector3 agent_pos = getPositionAgent(); + + if (mAvatarObject.notNull()) + { + // the z height is at the agent's feet + agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ]; } - else + + agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); + agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); + + // Don't let them go below ground, or too high. + agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], + mRegionp->getLandHeightRegion( agent_pos ), + LLWorld::getInstance()->getRegionMaxHeight() ); + // Send the CapReq + LLSD request; + LLSD body; + LLSD homeLocation; + + homeLocation["LocationId"] = LLSD::Integer(location_id); + homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); + homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); + + body["HomeLocation"] = homeLocation; + + // This awkward idiom warrants explanation. + // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new + // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder. + // In new code, define your reply/error methods on the same class as the + // sending method, bind them to local LLEventPump objects and pass those + // LLEventPump names in the request LLSD object. + // When testing old code, the new LLHomeLocationResponder object + // is referenced by an LLHTTPClient::ResponderPtr, so when the + // ResponderAdapter is deleted, the LLHomeLocationResponder will be too. + // We must trust that the underlying LLHTTPClient code will eventually + // fire either the reply callback or the error callback; either will cause + // the ResponderAdapter to delete itself. + LLSDMessage::ResponderAdapter* + adapter(new LLSDMessage::ResponderAdapter(new LLHomeLocationResponder())); + + request["message"] = "HomeLocation"; + request["payload"] = body; + request["reply"] = adapter->getReplyName(); + request["error"] = adapter->getErrorName(); + + gAgent.getRegion()->getCapAPI().post(request); + + const U32 HOME_INDEX = 1; + if( HOME_INDEX == location_id ) { - llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; + setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); } - } } +struct HomeLocationMapper: public LLCapabilityListener::CapabilityMapper +{ + // No reply message expected + HomeLocationMapper(): LLCapabilityListener::CapabilityMapper("HomeLocation") {} + virtual void buildMessage(LLMessageSystem* msg, + const LLUUID& agentID, + const LLUUID& sessionID, + const std::string& capabilityName, + const LLSD& payload) const + { + msg->newMessageFast(_PREHASH_SetStartLocationRequest); + msg->nextBlockFast( _PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agentID); + msg->addUUIDFast(_PREHASH_SessionID, sessionID); + msg->nextBlockFast( _PREHASH_StartLocationData); + // corrected by sim + msg->addStringFast(_PREHASH_SimName, ""); + msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger()); + msg->addVector3Fast(_PREHASH_LocationPos, + ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"])); + msg->addVector3Fast(_PREHASH_LocationLookAt, + ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"])); + } +}; +// Need an instance of this class so it will self-register +static HomeLocationMapper homeLocationMapper; + void LLAgent::requestStopMotion( LLMotion* motion ) { // Notify all avatars that a motion has stopped. @@ -5479,7 +5500,7 @@ void update_group_floaters(const LLUUID& group_id) gIMMgr->refresh(); } - gAgent.fireEvent(new LLEvent(&gAgent, "new group"), ""); + gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), ""); } // static diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 3174357a1a..23ff6cd594 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -119,7 +119,7 @@ inline bool operator==(const LLGroupData &a, const LLGroupData &b) // -class LLAgent : public LLObservable +class LLAgent : public LLOldEvents::LLObservable { LOG_CLASS(LLAgent); @@ -176,7 +176,7 @@ public: // Set the home data void setRegion(LLViewerRegion *regionp); LLViewerRegion *getRegion() const; - const LLHost& getRegionHost() const; + LLHost getRegionHost() const; std::string getSLURL() const; void updateAgentPosition(const F32 dt, const F32 yaw, const S32 mouse_x, const S32 mouse_y); // call once per frame to update position, angles radians diff --git a/indra/newview/llagentlanguage.h b/indra/newview/llagentlanguage.h index e313837883..45348a1e50 100644 --- a/indra/newview/llagentlanguage.h +++ b/indra/newview/llagentlanguage.h @@ -36,7 +36,7 @@ #include "llsingleton.h" // LLSingleton<> #include "llevent.h" -class LLAgentLanguage: public LLSingleton, public LLSimpleListener +class LLAgentLanguage: public LLSingleton, public LLOldEvents::LLSimpleListener { public: LLAgentLanguage(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index a613e6a14b..073b6b85fc 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -96,6 +96,7 @@ #include "lltexturecache.h" #include "lltexturefetch.h" #include "llimageworker.h" +#include "llevents.h" // The files below handle dependencies from cleanup. #include "llkeyframemotion.h" @@ -841,7 +842,14 @@ bool LLAppViewer::mainLoop() LLTimer debugTime; LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); joystick->setNeedsReset(true); - + + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + // As we do not (yet) send data on the mainloop LLEventPump that varies + // with each frame, no need to instantiate a new LLSD event object each + // time. Obviously, if that changes, just instantiate the LLSD at the + // point of posting. + LLSD newFrame; + // Handle messages while (!LLApp::isExiting()) { @@ -884,6 +892,9 @@ bool LLAppViewer::mainLoop() LLFloaterMemLeak::getInstance()->idle() ; } + // canonical per-frame event + mainloop.post(newFrame); + if (!LLApp::isExiting()) { pingMainloopTimeout("Main:JoystickKeyboard"); diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp new file mode 100644 index 0000000000..3277da8930 --- /dev/null +++ b/indra/newview/llcapabilitylistener.cpp @@ -0,0 +1,183 @@ +/** + * @file llcapabilitylistener.cpp + * @author Nat Goodspeed + * @date 2009-01-07 + * @brief Implementation for llcapabilitylistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "llviewerprecompiledheaders.h" +// associated header +#include "llcapabilitylistener.h" +// STL headers +#include +// std headers +// external library headers +#include +// other Linden headers +#include "stringize.h" +#include "llcapabilityprovider.h" + +class LLCapabilityListener::CapabilityMappers: public LLSingleton +{ +public: + void registerMapper(const LLCapabilityListener::CapabilityMapper*); + void unregisterMapper(const LLCapabilityListener::CapabilityMapper*); + const LLCapabilityListener::CapabilityMapper* find(const std::string& cap) const; + + struct DupCapMapper: public std::runtime_error + { + DupCapMapper(const std::string& what): + std::runtime_error(std::string("DupCapMapper: ") + what) + {} + }; + +private: + friend class LLSingleton; + CapabilityMappers(); + + typedef std::map CapabilityMap; + CapabilityMap mMap; +}; + +LLCapabilityListener::LLCapabilityListener(const std::string& name, + LLMessageSystem* messageSystem, + const LLCapabilityProvider& provider, + const LLUUID& agentID, + const LLUUID& sessionID): + mEventPump(name), + mMessageSystem(messageSystem), + mProvider(provider), + mAgentID(agentID), + mSessionID(sessionID) +{ + mEventPump.listen("self", boost::bind(&LLCapabilityListener::capListener, this, _1)); +} + +bool LLCapabilityListener::capListener(const LLSD& request) +{ + // Extract what we want from the request object. We do it all up front + // partly to document what we expect. + LLSD::String cap(request["message"]); + LLSD payload(request["payload"]); + LLSD::String reply(request["reply"]); + LLSD::String error(request["error"]); + LLSD::Real timeout(request["timeout"]); + // If the LLSD doesn't even have a "message" key, we doubt it was intended + // for this listener. + if (cap.empty()) + { + LL_ERRS("capListener") << "capability request event without 'message' key to '" + << getCapAPI().getName() + << "' on region\n" << mProvider.getDescription() + << LL_ENDL; + return false; // in case fatal-error function isn't + } + // Establish default timeout. This test relies on LLSD::asReal() returning + // exactly 0.0 for an undef value. + if (! timeout) + { + timeout = HTTP_REQUEST_EXPIRY_SECS; + } + // Look up the url for the requested capability name. + std::string url = mProvider.getCapability(cap); + if (! url.empty()) + { + // This capability is supported by the region to which we're talking. + LLHTTPClient::post(url, payload, + new LLSDMessage::EventResponder(LLEventPumps::instance(), + mProvider.getDescription(), + cap, reply, error), + LLSD(), // headers + timeout); + } + else + { + // Capability not supported -- do we have a registered mapper? + const CapabilityMapper* mapper = CapabilityMappers::instance().find(cap); + if (! mapper) // capability neither supported nor mapped + { + LL_ERRS("capListener") << "unsupported capability '" << cap << "' request to '" + << getCapAPI().getName() << "' on region\n" + << mProvider.getDescription() + << LL_ENDL; + } + else if (! mapper->getReplyName().empty()) // mapper expects reply support + { + LL_ERRS("capListener") << "Mapper for capability '" << cap + << "' requires unimplemented support for reply message '" + << mapper->getReplyName() + << "' on '" << getCapAPI().getName() << "' on region\n" + << mProvider.getDescription() + << LL_ENDL; + } + else + { + LL_INFOS("capListener") << "fallback invoked for capability '" << cap + << "' request to '" << getCapAPI().getName() + << "' on region\n" << mProvider.getDescription() + << LL_ENDL; + mapper->buildMessage(mMessageSystem, mAgentID, mSessionID, cap, payload); + mMessageSystem->sendReliable(mProvider.getHost()); + } + } + return false; +} + +LLCapabilityListener::CapabilityMapper::CapabilityMapper(const std::string& cap, const std::string& reply): + mCapName(cap), + mReplyName(reply) +{ + LLCapabilityListener::CapabilityMappers::instance().registerMapper(this); +} + +LLCapabilityListener::CapabilityMapper::~CapabilityMapper() +{ + LLCapabilityListener::CapabilityMappers::instance().unregisterMapper(this); +} + +LLSD LLCapabilityListener::CapabilityMapper::readResponse(LLMessageSystem* messageSystem) const +{ + return LLSD(); +} + +LLCapabilityListener::CapabilityMappers::CapabilityMappers() {} + +void LLCapabilityListener::CapabilityMappers::registerMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ + // Try to insert a new map entry by which we can look up the passed mapper + // instance. + std::pair inserted = + mMap.insert(CapabilityMap::value_type(mapper->getCapName(), mapper)); + // If we already have a mapper for that name, insert() merely located the + // existing iterator and returned false. It is a coding error to try to + // register more than one mapper for the same capability name. + if (! inserted.second) + { + throw DupCapMapper(std::string("Duplicate capability name ") + mapper->getCapName()); + } +} + +void LLCapabilityListener::CapabilityMappers::unregisterMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ + CapabilityMap::iterator found = mMap.find(mapper->getCapName()); + if (found != mMap.end()) + { + mMap.erase(found); + } +} + +const LLCapabilityListener::CapabilityMapper* +LLCapabilityListener::CapabilityMappers::find(const std::string& cap) const +{ + CapabilityMap::const_iterator found = mMap.find(cap); + if (found != mMap.end()) + { + return found->second; + } + return NULL; +} diff --git a/indra/newview/llcapabilitylistener.h b/indra/newview/llcapabilitylistener.h new file mode 100644 index 0000000000..061227e04d --- /dev/null +++ b/indra/newview/llcapabilitylistener.h @@ -0,0 +1,113 @@ +/** + * @file llcapabilitylistener.h + * @author Nat Goodspeed + * @date 2009-01-07 + * @brief Provide an event-based API for capability requests + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCAPABILITYLISTENER_H) +#define LL_LLCAPABILITYLISTENER_H + +#include "llevents.h" // LLEventPump +#include "llsdmessage.h" // LLSDMessage::ArgError +#include "llerror.h" // LOG_CLASS() + +class LLCapabilityProvider; +class LLSD; + +class LLCapabilityListener +{ + LOG_CLASS(LLCapabilityListener); +public: + LLCapabilityListener(const std::string& name, LLMessageSystem* messageSystem, + const LLCapabilityProvider& provider, + const LLUUID& agentID, const LLUUID& sessionID); + + /// Capability-request exception + typedef LLSDMessage::ArgError ArgError; + /// Get LLEventPump on which we listen for capability requests + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLEventPump& getCapAPI() { return mEventPump; } + + /** + * Base class for mapping an as-yet-undeployed capability name to a (pair + * of) LLMessageSystem message(s). To map a capability name to such + * messages, derive a subclass of CapabilityMapper and declare a static + * instance in a translation unit known to be loaded. The mapping is not + * region-specific. If an LLViewerRegion's capListener() receives a + * request for a supported capability, it will use the capability's URL. + * If not, it will look for an applicable CapabilityMapper subclass + * instance. + */ + class CapabilityMapper + { + public: + /** + * Base-class constructor. Typically your subclass constructor will + * pass these parameters as literals. + * @param cap the capability name handled by this (subclass) instance + * @param reply the name of the response LLMessageSystem message. Omit + * if the LLMessageSystem message you intend to send doesn't prompt a + * reply message, or if you already handle that message in some other + * way. + */ + CapabilityMapper(const std::string& cap, const std::string& reply = ""); + virtual ~CapabilityMapper(); + /// query the capability name + std::string getCapName() const { return mCapName; } + /// query the reply message name + std::string getReplyName() const { return mReplyName; } + /** + * Override this method to build the LLMessageSystem message we should + * send instead of the requested capability message. DO NOT send that + * message: that will be handled by the caller. + */ + virtual void buildMessage(LLMessageSystem* messageSystem, + const LLUUID& agentID, + const LLUUID& sessionID, + const std::string& capabilityName, + const LLSD& payload) const = 0; + /** + * Override this method if you pass a non-empty @a reply + * LLMessageSystem message name to the constructor: that is, if you + * expect to receive an LLMessageSystem message in response to the + * message you constructed in buildMessage(). If you don't pass a @a + * reply message name, you need not override this method as it won't + * be called. + * + * Using LLMessageSystem message-reading operations, your + * readResponse() override should construct and return an LLSD object + * of the form you expect to receive from the real implementation of + * the capability you intend to invoke, when it finally goes live. + */ + virtual LLSD readResponse(LLMessageSystem* messageSystem) const; + + private: + const std::string mCapName; + const std::string mReplyName; + }; + +private: + /// Bind the LLCapabilityProvider passed to our ctor + const LLCapabilityProvider& mProvider; + + /// Post an event to this LLEventPump to invoke a capability message on + /// the bound LLCapabilityProvider's server + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLEventStream mEventPump; + + LLMessageSystem* mMessageSystem; + LLUUID mAgentID, mSessionID; + + /// listener to process capability requests + bool capListener(const LLSD&); + + /// helper class for capListener() + class CapabilityMappers; +}; + +#endif /* ! defined(LL_LLCAPABILITYLISTENER_H) */ diff --git a/indra/newview/llcapabilityprovider.h b/indra/newview/llcapabilityprovider.h new file mode 100644 index 0000000000..0ddb2b6cb9 --- /dev/null +++ b/indra/newview/llcapabilityprovider.h @@ -0,0 +1,39 @@ +/** + * @file llcapabilityprovider.h + * @author Nat Goodspeed + * @date 2009-01-07 + * @brief Interface by which to reference (e.g.) LLViewerRegion to obtain a + * capability. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCAPABILITYPROVIDER_H) +#define LL_LLCAPABILITYPROVIDER_H + +#include "llhost.h" +#include + +/// Interface for obtaining a capability URL, given a capability name +class LLCapabilityProvider +{ +public: + virtual ~LLCapabilityProvider() {} + /** + * Get a capability URL, given a capability name. Returns empty string if + * no such capability is defined on this provider. + */ + virtual std::string getCapability(const std::string& name) const = 0; + /** + * Get host to which to send that capability request. + */ + virtual LLHost getHost() const = 0; + /** + * Describe this LLCapabilityProvider for logging etc. + */ + virtual std::string getDescription() const = 0; +}; + +#endif /* ! defined(LL_LLCAPABILITYPROVIDER_H) */ diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index 011774fa5e..65035d9b5c 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -59,6 +59,8 @@ #include "llimview.h" #include "lltrans.h" +using namespace LLOldEvents; + // static std::map LLFloaterGroupPicker::sInstances; diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index b30d40581f..d3fb405b42 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -48,7 +48,7 @@ #include "llfloater.h" #include #include -#include +#include class LLUICtrl; class LLTextBox; @@ -63,7 +63,7 @@ public: ~LLFloaterGroupPicker(); // Note: Don't return connection; use boost::bind + boost::signal::trackable to disconnect slots - typedef boost::signal signal_t; + typedef boost::signals2::signal signal_t; void setSelectGroupCallback(const signal_t::slot_type& cb) { mGroupSelectSignal.connect(cb); } void setPowersMask(U64 powers_mask); BOOL postBuild(); @@ -87,14 +87,14 @@ protected: static instance_map_t sInstances; }; -class LLPanelGroups : public LLPanel, public LLSimpleListener +class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener { public: LLPanelGroups(); virtual ~LLPanelGroups(); //LLEventListener - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); // clear the group list, and get a fresh set of info. void reset(); diff --git a/indra/newview/llfolderview.h b/indra/newview/llfolderview.h index 1b128d84ee..d8eb9008f0 100644 --- a/indra/newview/llfolderview.h +++ b/indra/newview/llfolderview.h @@ -763,7 +763,7 @@ public: void setFilterPermMask(PermissionMask filter_perm_mask) { mFilter.setFilterPermissions(filter_perm_mask); } void setAllowMultiSelect(BOOL allow) { mAllowMultiSelect = allow; } - typedef boost::signal& items, BOOL user_action)> signal_t; + typedef boost::signals2::signal& items, BOOL user_action)> signal_t; void setSelectCallback(const signal_t::slot_type& cb) { mSelectSignal.connect(cb); } LLInventoryFilter* getFilter() { return &mFilter; } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index fe8d1c4844..89f9242b06 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -89,6 +89,8 @@ #include "llfloateropenobject.h" #include "lltrans.h" +using namespace LLOldEvents; + // Helpers // bug in busy count inc/dec right now, logic is complex... do we really need it? void inc_busy_count() diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 7d18616a13..53c7484e72 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -58,6 +58,8 @@ static LLRegisterWidget r1("net_map"); +using namespace LLOldEvents; + const F32 MAP_SCALE_MIN = 64; const F32 MAP_SCALE_MID = 172; const F32 MAP_SCALE_MAX = 512; diff --git a/indra/newview/llteleporthistory.h b/indra/newview/llteleporthistory.h index 631857d7c8..fc061075c9 100644 --- a/indra/newview/llteleporthistory.h +++ b/indra/newview/llteleporthistory.h @@ -38,7 +38,7 @@ #include #include #include -#include +#include /** @@ -206,7 +206,7 @@ private: * Using this connection we get notified when a teleport finishes * or initial location update occurs. */ - boost::signals::connection mTeleportFinishedConn; + boost::signals2::connection mTeleportFinishedConn; }; #endif diff --git a/indra/newview/lltoolpipette.h b/indra/newview/lltoolpipette.h index fcccafe1a4..533e8a7c95 100644 --- a/indra/newview/lltoolpipette.h +++ b/indra/newview/lltoolpipette.h @@ -41,7 +41,7 @@ #include "lltool.h" #include "lltextureentry.h" #include -#include +#include class LLViewerObject; class LLPickInfo; @@ -59,7 +59,7 @@ public: virtual BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect *sticky_rect_screen); // Note: Don't return connection; use boost::bind + boost::signal::trackable to disconnect slots - typedef boost::signal signal_t; + typedef boost::signals2::signal signal_t; void setToolSelectCallback(const signal_t::slot_type& cb) { mSignal.connect(cb); } void setResult(BOOL success, const std::string& msg); diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 52c49dd05d..b1482d5ce4 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -762,56 +762,39 @@ void move_inventory_item( gAgent.sendReliableMessage(); } -class LLCopyInventoryFromNotecardResponder : public LLHTTPClient::Responder -{ -public: - //If we get back a normal response, handle it here - virtual void result(const LLSD& content) - { - // What do we do here? - llinfos << "CopyInventoryFromNotecard request successful." << llendl; - } - - //If we get back an error (not found, etc...), handle it here - virtual void error(U32 status, const std::string& reason) - { - llinfos << "LLCopyInventoryFromNotecardResponder::error " - << status << ": " << reason << llendl; - } -}; - void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecard_inv_id, const LLInventoryItem *src, U32 callback_id) { - LLSD body; LLViewerRegion* viewer_region = NULL; - if(object_id.notNull()) - { - LLViewerObject* vo = gObjectList.findObject(object_id); - if(vo) - { - viewer_region = vo->getRegion(); - } + LLViewerObject* vo = NULL; + if (object_id.notNull() && (vo = gObjectList.findObject(object_id)) != NULL) + { + viewer_region = vo->getRegion(); } // Fallback to the agents region if for some reason the // object isn't found in the viewer. - if(!viewer_region) + if (! viewer_region) { viewer_region = gAgent.getRegion(); } - if(viewer_region) + if (! viewer_region) { - std::string url = viewer_region->getCapability("CopyInventoryFromNotecard"); - if (!url.empty()) - { - body["notecard-id"] = notecard_inv_id; - body["object-id"] = object_id; - body["item-id"] = src->getUUID(); - body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType()); - body["callback-id"] = (LLSD::Integer)callback_id; - - LLHTTPClient::post(url, body, new LLCopyInventoryFromNotecardResponder()); - } - } + LL_WARNS("copy_inventory_from_notecard") << "Can't find region from object_id " + << object_id << " or gAgent" + << LL_ENDL; + return; + } + + LLSD request, body; + body["notecard-id"] = notecard_inv_id; + body["object-id"] = object_id; + body["item-id"] = src->getUUID(); + body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType()); + body["callback-id"] = (LLSD::Integer)callback_id; + + request["message"] = "CopyInventoryFromNotecard"; + request["payload"] = body; + + viewer_region->getCapAPI().post(request); } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index c2724b7cdd..1b3fd5d49b 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -46,7 +46,7 @@ #include "lluuid.h" #include // for SkinFolder listener -#include +#include // Implementation functions not exported into header file diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 0abdaff4b6..f70e5ad242 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1,4 +1,4 @@ - /** +/** * @file llviewermenu.cpp * @brief Builds menus out of items. * @@ -210,6 +210,7 @@ #include "lltexlayer.h" using namespace LLVOAvatarDefines; +using namespace LLOldEvents; BOOL enable_land_build(void*); BOOL enable_object_build(void*); diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 97b121d71d..1352f2c72f 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -71,6 +71,8 @@ // system libraries #include +using namespace LLOldEvents; + class LLFileEnableSaveAs : public view_listener_t { bool handleEvent(const LLSD& userdata) diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index 20723ec360..da9587a359 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -2392,12 +2392,12 @@ LLViewerImage* LLViewerParcelMgr::getPassImage() const return sPassImage; } -boost::signals::connection LLViewerParcelMgr::setAgentParcelChangedCallback(parcel_changed_callback_t cb) +boost::signals2::connection LLViewerParcelMgr::setAgentParcelChangedCallback(parcel_changed_callback_t cb) { return mAgentParcelChangedSignal.connect(cb); } -boost::signals::connection LLViewerParcelMgr::setTeleportFinishedCallback(parcel_changed_callback_t cb) +boost::signals2::connection LLViewerParcelMgr::setTeleportFinishedCallback(parcel_changed_callback_t cb) { return mTeleportFinishedSignal.connect(cb); } diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h index 2f3583e33b..3426fda636 100644 --- a/indra/newview/llviewerparcelmgr.h +++ b/indra/newview/llviewerparcelmgr.h @@ -41,8 +41,8 @@ #include "llui.h" #include -#include -#include +#include +#include class LLUUID; class LLMessageSystem; @@ -83,8 +83,8 @@ class LLViewerParcelMgr : public LLSingleton { public: - typedef boost::function parcel_changed_callback_t; - typedef boost::signal parcel_changed_signal_t; + typedef boost::function parcel_changed_callback_t; + typedef boost::signals2::signal parcel_changed_signal_t; LLViewerParcelMgr(); ~LLViewerParcelMgr(); @@ -263,8 +263,8 @@ public: // the agent is banned or not in the allowed group BOOL isCollisionBanned(); - boost::signals::connection setAgentParcelChangedCallback(parcel_changed_callback_t cb); - boost::signals::connection setTeleportFinishedCallback(parcel_changed_callback_t cb); + boost::signals2::connection setAgentParcelChangedCallback(parcel_changed_callback_t cb); + boost::signals2::connection setTeleportFinishedCallback(parcel_changed_callback_t cb); void onTeleportFinished(); static BOOL isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power); diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 47619919b3..89ebe0cec8 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -66,6 +66,11 @@ #include "llvoclouds.h" #include "llworld.h" #include "llspatialpartition.h" +#include "stringize.h" + +#ifdef LL_WINDOWS + #pragma warning(disable:4355) +#endif // Viewer object cache version, change if object update // format changes. JC @@ -173,7 +178,18 @@ LLViewerRegion::LLViewerRegion(const U64 &handle, mCacheEntriesCount(0), mCacheID(), mEventPoll(NULL), - mReleaseNotesRequested(FALSE) + mReleaseNotesRequested(FALSE), + // I'd prefer to set the LLCapabilityListener name to match the region + // name -- it's disappointing that's not available at construction time. + // We could instead store an LLCapabilityListener*, making + // setRegionNameAndZone() replace the instance. Would that pose + // consistency problems? Can we even request a capability before calling + // setRegionNameAndZone()? + // For testability -- the new Michael Feathers paradigm -- + // LLCapabilityListener binds all the globals it expects to need at + // construction time. + mCapabilityListener(host.getString(), gMessageSystem, *this, + gAgent.getID(), gAgent.getSessionID()) { mWidth = region_width_meters; mOriginGlobal = from_region_handle(handle); @@ -224,7 +240,6 @@ LLViewerRegion::LLViewerRegion(const U64 &handle, mObjectPartition.push_back(new LLBridgePartition()); //PARTITION_BRIDGE mObjectPartition.push_back(new LLHUDParticlePartition());//PARTITION_HUD_PARTICLE mObjectPartition.push_back(NULL); //PARTITION_NONE - } @@ -770,6 +785,15 @@ std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion) s << "{ "; s << region.mHost; s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n"; + std::string name(region.getName()), zone(region.getZoning()); + if (! name.empty()) + { + s << " mName = " << name << '\n'; + } + if (! zone.empty()) + { + s << " mZoning = " << zone << '\n'; + } s << "}"; return s; } @@ -1514,3 +1538,8 @@ void LLViewerRegion::showReleaseNotes() LLWeb::loadURL(url); mReleaseNotesRequested = FALSE; } + +std::string LLViewerRegion::getDescription() const +{ + return stringize(*this); +} diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index f43167f93a..35f374a4c8 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -49,6 +49,8 @@ #include "lldatapacker.h" #include "llvocache.h" #include "llweb.h" +#include "llcapabilityprovider.h" +#include "llcapabilitylistener.h" // Surface id's #define LAND 1 @@ -66,8 +68,9 @@ class LLSurface; class LLVOCache; class LLVOCacheEntry; class LLSpatialPartition; +class LLEventPump; -class LLViewerRegion +class LLViewerRegion: public LLCapabilityProvider // implements this interface { public: //MUST MATCH THE ORDER OF DECLARATION IN CONSTRUCTOR @@ -226,11 +229,19 @@ public: // Get/set named capability URLs for this region. void setSeedCapability(const std::string& url); void setCapability(const std::string& name, const std::string& url); - std::string getCapability(const std::string& name) const; + // implements LLCapabilityProvider + virtual std::string getCapability(const std::string& name) const; static bool isSpecialCapabilityName(const std::string &name); void logActiveCapabilities() const; - const LLHost &getHost() const { return mHost; } + /// Capability-request exception + typedef LLCapabilityListener::ArgError ArgError; + /// Get LLEventPump on which we listen for capability requests + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLEventPump& getCapAPI() { return mCapabilityListener.getCapAPI(); } + + /// implements LLCapabilityProvider + virtual LLHost getHost() const { return mHost; } const U64 &getHandle() const { return mHandle; } LLSurface &getLand() const { return *mLandp; } @@ -274,6 +285,8 @@ public: void calculateCameraDistance(); friend std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion); + /// implements LLCapabilityProvider + virtual std::string getDescription() const; LLSpatialPartition* getSpatialPartition(U32 type); public: @@ -391,6 +404,11 @@ private: LLEventPoll* mEventPoll; + /// Post an event to this LLCapabilityListener to invoke a capability message on + /// this LLViewerRegion's server + /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) + LLCapabilityListener mCapabilityListener; + private: bool mAlive; // can become false if circuit disconnects @@ -458,5 +476,3 @@ inline BOOL LLViewerRegion::getReleaseNotesRequested() const } #endif - - diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index bf779c427a..73065c5c00 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -40,6 +40,8 @@ #include "llviewerstats.h" #include "lldatapacker.h" +using namespace LLOldEvents; + // consts // The viewer is allowed to set the under-the-hood bandwidth to 50% diff --git a/indra/newview/tests/llcapabilitylistener_test.cpp b/indra/newview/tests/llcapabilitylistener_test.cpp new file mode 100644 index 0000000000..3c5f6fad2d --- /dev/null +++ b/indra/newview/tests/llcapabilitylistener_test.cpp @@ -0,0 +1,274 @@ +/** + * @file llcapabilitylistener_test.cpp + * @author Nat Goodspeed + * @date 2008-12-31 + * @brief Test for llcapabilitylistener.cpp. + * + * $LicenseInfo:firstyear=2008&license=internal$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "../llviewerprecompiledheaders.h" +// Own header +#include "../llcapabilitylistener.h" +// STL headers +#include +#include +#include +// std headers +// external library headers +#include "boost/bind.hpp" +// other Linden headers +#include "../test/lltut.h" +#include "../llcapabilityprovider.h" +#include "lluuid.h" +#include "llerrorcontrol.h" +#include "tests/networkio.h" +#include "tests/commtest.h" +#include "stringize.h" + +#if defined(LL_WINDOWS) +#pragma warning(disable: 4355) // using 'this' in base-class ctor initializer expr +#endif + +/***************************************************************************** +* TestCapabilityProvider +*****************************************************************************/ +struct TestCapabilityProvider: public LLCapabilityProvider +{ + TestCapabilityProvider(const LLHost& host): + mHost(host) + {} + + std::string getCapability(const std::string& cap) const + { + CapMap::const_iterator found = mCaps.find(cap); + if (found != mCaps.end()) + return found->second; + // normal LLViewerRegion lookup failure mode + return ""; + } + void setCapability(const std::string& cap, const std::string& url) + { + mCaps[cap] = url; + } + LLHost getHost() const { return mHost; } + std::string getDescription() const { return "TestCapabilityProvider"; } + + LLHost mHost; + typedef std::map CapMap; + CapMap mCaps; +}; + +/***************************************************************************** +* Dummy LLMessageSystem methods +*****************************************************************************/ +/*==========================================================================*| +// This doesn't work because we're already linking in llmessage.a, and we get +// duplicate-symbol errors from the linker. Perhaps if I wanted to go through +// the exercise of providing dummy versions of every single symbol defined in +// message.o -- maybe some day. +typedef std::vector< std::pair > StringPairVector; +StringPairVector call_history; + +S32 LLMessageSystem::sendReliable(const LLHost& host) +{ + call_history.push_back(StringPairVector::value_type("sendReliable", stringize(host))); + return 0; +} +|*==========================================================================*/ + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llcapears_data: public commtest_data + { + TestCapabilityProvider provider; + LLCapabilityListener regionListener; + LLEventPump& regionPump; + + llcapears_data(): + provider(host), + regionListener("testCapabilityListener", NULL, provider, LLUUID(), LLUUID()), + regionPump(regionListener.getCapAPI()) + { + provider.setCapability("good", server + "capability-test"); + provider.setCapability("fail", server + "fail"); + } + }; + typedef test_group llcapears_group; + typedef llcapears_group::object llcapears_object; + llcapears_group llsdmgr("llcapabilitylistener"); + + struct CaptureError: public LLError::OverrideFatalFunction + { + CaptureError(): + LLError::OverrideFatalFunction(boost::bind(&CaptureError::operator(), this, _1)) + { + LLError::setPrintLocation(false); + } + + struct FatalException: public std::runtime_error + { + FatalException(const std::string& what): std::runtime_error(what) {} + }; + + void operator()(const std::string& message) + { + error = message; + throw FatalException(message); + } + + std::string error; + }; + + template<> template<> + void llcapears_object::test<1>() + { + LLSD request, body; + body["data"] = "yes"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + std::string threw; + try + { + CaptureError capture; + regionPump.post(request); + } + catch (const CaptureError::FatalException& e) + { + threw = e.what(); + } + ensure_contains("missing capability name", threw, "without 'message' key"); + } + + template<> template<> + void llcapears_object::test<2>() + { + LLSD request, body; + body["data"] = "yes"; + request["message"] = "good"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + regionPump.post(request); + ensure("got response", netio.pump()); + ensure("success response", success); + ensure_equals(result.asString(), "success"); + + body["status"] = 499; + body["reason"] = "custom error message"; + request["message"] = "fail"; + request["payload"] = body; + regionPump.post(request); + ensure("got response", netio.pump()); + ensure("failure response", ! success); + ensure_equals(result["status"].asInteger(), body["status"].asInteger()); + ensure_equals(result["reason"].asString(), body["reason"].asString()); + } + + template<> template<> + void llcapears_object::test<3>() + { + LLSD request, body; + body["data"] = "yes"; + request["message"] = "unknown"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + std::string threw; + try + { + CaptureError capture; + regionPump.post(request); + } + catch (const CaptureError::FatalException& e) + { + threw = e.what(); + } + ensure_contains("bad capability name", threw, "unsupported capability"); + } + + struct TestMapper: public LLCapabilityListener::CapabilityMapper + { + // Instantiator gets to specify whether mapper expects a reply. + // I'd really like to be able to test CapabilityMapper::buildMessage() + // functionality, too, but -- even though LLCapabilityListener accepts + // the LLMessageSystem* that it passes to CapabilityMapper -- + // LLMessageSystem::sendReliable(const LLHost&) isn't virtual, so it's + // not helpful to pass a subclass instance. I suspect that making any + // LLMessageSystem methods virtual would provoke howls of outrage, + // given how heavily it's used. Nor can I just provide a local + // definition of LLMessageSystem::sendReliable(const LLHost&) because + // we're already linking in the rest of message.o via llmessage.a, and + // that produces duplicate-symbol link errors. + TestMapper(const std::string& replyMessage = std::string()): + LLCapabilityListener::CapabilityMapper("test", replyMessage) + {} + virtual void buildMessage(LLMessageSystem* msg, + const LLUUID& agentID, + const LLUUID& sessionID, + const std::string& capabilityName, + const LLSD& payload) const + { + msg->newMessageFast(_PREHASH_SetStartLocationRequest); + msg->nextBlockFast( _PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agentID); + msg->addUUIDFast(_PREHASH_SessionID, sessionID); + msg->nextBlockFast( _PREHASH_StartLocationData); + // corrected by sim + msg->addStringFast(_PREHASH_SimName, ""); + msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger()); +/*==========================================================================*| + msg->addVector3Fast(_PREHASH_LocationPos, + ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"])); + msg->addVector3Fast(_PREHASH_LocationLookAt, + ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"])); +|*==========================================================================*/ + } + }; + + template<> template<> + void llcapears_object::test<4>() + { + TestMapper testMapper("WantReply"); + LLSD request, body; + body["data"] = "yes"; + request["message"] = "test"; + request["payload"] = body; + request["reply"] = replyPump.getName(); + request["error"] = errorPump.getName(); + std::string threw; + try + { + CaptureError capture; + regionPump.post(request); + } + catch (const CaptureError::FatalException& e) + { + threw = e.what(); + } + ensure_contains("capability mapper wants reply", threw, "unimplemented support for reply message"); + } + + template<> template<> + void llcapears_object::test<5>() + { + TestMapper testMapper; + std::string threw; + try + { + TestMapper testMapper2; + } + catch (const std::runtime_error& e) + { + threw = e.what(); + } + ensure_contains("no dup cap mapper", threw, "DupCapMapper"); + } +} diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index c74ef06636..88ef15a8d9 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -13,6 +13,7 @@ include(LLXML) include(LScript) include(Linking) include(Tut) +include(Boost) include_directories( ${LLCOMMON_INCLUDE_DIRS} @@ -35,7 +36,9 @@ set(test_SOURCE_FILES llbuffer_tut.cpp lldate_tut.cpp lldoubledispatch_tut.cpp + lldependencies_tut.cpp llerror_tut.cpp + llevents_tut.cpp llhost_tut.cpp llhttpdate_tut.cpp llhttpclient_tut.cpp @@ -73,6 +76,7 @@ set(test_SOURCE_FILES math.cpp message_tut.cpp reflection_tut.cpp + stringize_tut.cpp test.cpp v2math_tut.cpp v3color_tut.cpp diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp new file mode 100644 index 0000000000..e401f89b22 --- /dev/null +++ b/indra/test/llevents_tut.cpp @@ -0,0 +1,793 @@ +/** + * @file llevents_tut.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Test of llevents.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +// UGLY HACK! We want to verify state internal to the classes without +// providing public accessors. +#define testable public +#include "llevents.h" +#undef testable +// STL headers +// std headers +#include +#include +// external library headers +#include +#include +#include +// other Linden headers +#include "lltut.h" +#include "stringize.h" + +using boost::assign::list_of; + +/***************************************************************************** +* test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +class Listener +{ +public: + Listener(const std::string& name): + mName(name) + { +// std::cout << *this << ": ctor\n"; + } + Listener(const Listener& that): + mName(that.mName), + mLastEvent(that.mLastEvent) + { +// std::cout << *this << ": copy\n"; + } + virtual ~Listener() + { +// std::cout << *this << ": dtor\n"; + } + std::string getName() const { return mName; } + bool call(const LLSD& event) + { +// std::cout << *this << "::call(" << event << ")\n"; + mLastEvent = event; + return false; + } + bool callstop(const LLSD& event) + { +// std::cout << *this << "::callstop(" << event << ")\n"; + mLastEvent = event; + return true; + } + LLSD getLastEvent() const + { +// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; + return mLastEvent; + } + void reset(const LLSD& to = LLSD()) + { +// std::cout << *this << "::reset(" << to << ")\n"; + mLastEvent = to; + } + +private: + std::string mName; + LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ + out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; + return out; +} + +struct Collect +{ + bool add(const std::string& bound, const LLSD& event) + { + result.push_back(bound); + return false; + } + void clear() { result.clear(); } + typedef std::vector StringList; + StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ + out << '('; + Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); + if (begin != end) + { + out << '"' << *begin << '"'; + while (++begin != end) + { + out << ", \"" << *begin << '"'; + } + } + out << ')'; + return out; +} + +template +T make(const T& value) { return value; } + +/***************************************************************************** +* tut test group +*****************************************************************************/ +namespace tut +{ + struct events_data + { + events_data(): + pumps(LLEventPumps::instance()), + listener0("first"), + listener1("second") + {} + LLEventPumps& pumps; + Listener listener0; + Listener listener1; + + void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got) + { + ensure_equals(STRINGIZE(listener << ' ' << desc), + listener.getLastEvent().asInteger(), got); + } + }; + typedef test_group events_group; + typedef events_group::object events_object; + tut::events_group evgr("events"); + + template<> template<> + void events_object::test<1>() + { + set_test_name("basic operations"); + // Now there's a static constructor in llevents.cpp that registers on + // the "mainloop" pump to call LLEventPumps::flush(). + // Actually -- having to modify this to track the statically- + // constructed pumps in other TUT modules in this giant monolithic test + // executable isn't such a hot idea. +// ensure_equals("initial pump", pumps.mPumpMap.size(), 1); + size_t initial_pumps(pumps.mPumpMap.size()); + LLEventPump& per_frame(pumps.obtain("per-frame")); + ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps+1); + // Verify that per_frame was instantiated as an LLEventStream. + ensure("LLEventStream leaf class", dynamic_cast(&per_frame)); + ensure("enabled", per_frame.enabled()); + // Trivial test, but posting an event to an EventPump with no + // listeners should not blow up. The test is relevant because defining + // a boost::signal with a non-void return signature, using the default + // combiner, blows up if there are no listeners. This is because the + // default combiner is defined to return the value returned by the + // last listener, which is meaningless if there were no listeners. + per_frame.post(0); + // NOTE: boost::bind() saves its arguments by VALUE! If you pass an + // object instance rather than a pointer, you'll end up binding to an + // internal copy of that instance! Use boost::ref() to capture a + // reference instead. + LLBoundListener connection = per_frame.listen(listener0.getName(), + boost::bind(&Listener::call, + boost::ref(listener0), + _1)); + ensure("connected", connection.connected()); + ensure("not blocked", ! connection.blocked()); + per_frame.post(1); + check_listener("received", listener0, 1); + { // block the connection + LLEventPump::Blocker block(connection); + ensure("blocked", connection.blocked()); + per_frame.post(2); + check_listener("not updated", listener0, 1); + } // unblock + ensure("unblocked", ! connection.blocked()); + per_frame.post(3); + check_listener("unblocked", listener0, 3); + LLBoundListener sameConnection = per_frame.getListener(listener0.getName()); + ensure("still connected", sameConnection.connected()); + ensure("still not blocked", ! sameConnection.blocked()); + { // block it again + LLEventPump::Blocker block(sameConnection); + ensure("re-blocked", sameConnection.blocked()); + per_frame.post(4); + check_listener("re-blocked", listener0, 3); + } // unblock + bool threw = false; + try + { + per_frame.listen(listener0.getName(), // note bug, dup name + boost::bind(&Listener::call, boost::ref(listener1), _1)); + } + catch (const LLEventPump::DupListenerName& e) + { + threw = true; + ensure_equals(e.what(), + std::string("DupListenerName: " + "Attempt to register duplicate listener name '") + + listener0.getName() + + "' on " + typeid(per_frame).name() + " '" + per_frame.getName() + "'"); + } + ensure("threw DupListenerName", threw); + // do it right this time + per_frame.listen(listener1.getName(), + boost::bind(&Listener::call, boost::ref(listener1), _1)); + per_frame.post(5); + check_listener("got", listener0, 5); + check_listener("got", listener1, 5); + per_frame.enable(false); + per_frame.post(6); + check_listener("didn't get", listener0, 5); + check_listener("didn't get", listener1, 5); + per_frame.enable(); + per_frame.post(7); + check_listener("got", listener0, 7); + check_listener("got", listener1, 7); + per_frame.stopListening(listener0.getName()); + ensure("disconnected 0", ! connection.connected()); + ensure("disconnected 1", ! sameConnection.connected()); + per_frame.post(8); + check_listener("disconnected", listener0, 7); + check_listener("still connected", listener1, 8); + per_frame.stopListening(listener1.getName()); + per_frame.post(9); + check_listener("disconnected", listener1, 8); + } + + template<> template<> + void events_object::test<2>() + { + set_test_name("callstop() returning true"); + LLEventPump& per_frame(pumps.obtain("per-frame")); + listener0.reset(0); + listener1.reset(0); + LLBoundListener bound0 = per_frame.listen(listener0.getName(), + boost::bind(&Listener::callstop, + boost::ref(listener0), + _1)); + LLBoundListener bound1 = per_frame.listen(listener1.getName(), + boost::bind(&Listener::call, + boost::ref(listener1), + _1), + // after listener0 + make(list_of(listener0.getName()))); + ensure("enabled", per_frame.enabled()); + ensure("connected 0", bound0.connected()); + ensure("unblocked 0", ! bound0.blocked()); + ensure("connected 1", bound1.connected()); + ensure("unblocked 1", ! bound1.blocked()); + per_frame.post(1); + check_listener("got", listener0, 1); + // Because listener0.callstop() returns true, control never reaches listener1.call(). + check_listener("got", listener1, 0); + } + + bool chainEvents(Listener& someListener, const LLSD& event) + { + // Make this call so we can watch for side effects for test purposes. + someListener.call(event); + // This function represents a recursive event chain -- or some other + // scenario in which an event handler raises additional events. + int value = event.asInteger(); + if (value) + { + LLEventPumps::instance().obtain("login").post(value - 1); + } + return false; + } + + template<> template<> + void events_object::test<3>() + { + set_test_name("LLEventQueue delayed action"); + // This access is NOT legal usage: we can do it only because we're + // hacking private for test purposes. Normally we'd either compile in + // a particular name, or (later) edit a config file. + pumps.mQueueNames.insert("login"); + LLEventPump& login(pumps.obtain("login")); + // The "mainloop" pump is special: posting on that implicitly calls + // LLEventPumps::flush(), which in turn should flush our "login" + // LLEventQueue. + LLEventPump& mainloop(pumps.obtain("mainloop")); + ensure("LLEventQueue leaf class", dynamic_cast(&login)); + login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.reset(0); + login.post(1); + check_listener("waiting for queued event", listener0, 0); + mainloop.post(LLSD()); + check_listener("got queued event", listener0, 1); + login.stopListening(listener0.getName()); + // Verify that when an event handler posts a new event on the same + // LLEventQueue, it doesn't get processed in the same flush() call -- + // it waits until the next flush() call. + listener0.reset(17); + login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1)); + login.post(1); + check_listener("chainEvents(1) not yet called", listener0, 17); + mainloop.post(LLSD()); + check_listener("chainEvents(1) called", listener0, 1); + mainloop.post(LLSD()); + check_listener("chainEvents(0) called", listener0, 0); + mainloop.post(LLSD()); + check_listener("chainEvents(-1) not called", listener0, 0); + login.stopListening("chainEvents"); + } + + template<> template<> + void events_object::test<4>() + { + set_test_name("explicitly-instantiated LLEventStream"); + // Explicitly instantiate an LLEventStream, and verify that it + // self-registers with LLEventPumps + size_t registered = pumps.mPumpMap.size(); + size_t owned = pumps.mOurPumps.size(); + LLEventPump* localInstance; + { + LLEventStream myEventStream("stream"); + localInstance = &myEventStream; + LLEventPump& stream(pumps.obtain("stream")); + ensure("found named LLEventStream instance", &stream == localInstance); + ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1); + ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned); + } // destroy myEventStream -- should unregister + ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered); + ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned); + LLEventPump& stream(pumps.obtain("stream")); + ensure("new LLEventStream instance", &stream != localInstance); + ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1); + ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1); + } + + template<> template<> + void events_object::test<5>() + { + set_test_name("stopListening()"); + LLEventPump& login(pumps.obtain("login")); + login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + login.stopListening(listener0.getName()); + // should not throw because stopListening() should have removed name + login.listen(listener0.getName(), + boost::bind(&Listener::callstop, boost::ref(listener0), _1)); + LLBoundListener wrong = login.getListener("bogus"); + ensure("bogus connection disconnected", ! wrong.connected()); + ensure("bogus connection blocked", wrong.blocked()); + } + + template<> template<> + void events_object::test<6>() + { + set_test_name("chaining LLEventPump instances"); + LLEventPump& upstream(pumps.obtain("upstream")); + // One potentially-useful construct is to chain LLEventPumps together. + // Among other things, this allows you to turn subsets of listeners on + // and off in groups. + LLEventPump& filter0(pumps.obtain("filter0")); + LLEventPump& filter1(pumps.obtain("filter1")); + upstream.listen(filter0.getName(), + boost::bind(&LLEventPump::post, boost::ref(filter0), _1)); + upstream.listen(filter1.getName(), + boost::bind(&LLEventPump::post, boost::ref(filter1), _1)); + filter0.listen(listener0.getName(), + boost::bind(&Listener::call, boost::ref(listener0), _1)); + filter1.listen(listener1.getName(), + boost::bind(&Listener::call, boost::ref(listener1), _1)); + listener0.reset(0); + listener1.reset(0); + upstream.post(1); + check_listener("got unfiltered", listener0, 1); + check_listener("got unfiltered", listener1, 1); + filter0.enable(false); + upstream.post(2); + check_listener("didn't get filtered", listener0, 1); + check_listener("got filtered", listener1, 2); + } + + template<> template<> + void events_object::test<7>() + { + set_test_name("listener dependency order"); + typedef LLEventPump::NameList NameList; + typedef Collect::StringList StringList; + LLEventPump& button(pumps.obtain("button")); + Collect collector; + button.listen("Mary", + boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), + // state that "Mary" must come after "checked" + make(list_of("checked"))); + button.listen("checked", + boost::bind(&Collect::add, boost::ref(collector), "checked", _1), + // "checked" must come after "spot" + make(list_of("spot"))); + button.listen("spot", + boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); + button.post(1); + ensure_equals(collector.result, make(list_of("spot")("checked")("Mary"))); + collector.clear(); + button.stopListening("Mary"); + button.listen("Mary", + boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), + LLEventPump::empty, // no after dependencies + // now "Mary" must come before "spot" + make(list_of("spot"))); + button.post(2); + ensure_equals(collector.result, make(list_of("Mary")("spot")("checked"))); + collector.clear(); + button.stopListening("spot"); + std::string threw; + try + { + button.listen("spot", + boost::bind(&Collect::add, boost::ref(collector), "spot", _1), + // after "Mary" and "checked" -- whoops! + make(list_of("Mary")("checked"))); + } + catch (const LLEventPump::Cycle& e) + { + threw = e.what(); +// std::cout << "Caught: " << e.what() << '\n'; + } + // Obviously the specific wording of the exception text can + // change; go ahead and change the test to match. + // Establish that it contains: + // - the name and runtime type of the LLEventPump + ensure_contains("LLEventPump type", threw, typeid(button).name()); + ensure_contains("LLEventPump name", threw, "'button'"); + // - the name of the new listener that caused the problem + ensure_contains("new listener name", threw, "'spot'"); + // - a synopsis of the problematic dependencies. + ensure_contains("cyclic dependencies", threw, + "\"Mary\" -> before (\"spot\")"); + ensure_contains("cyclic dependencies", threw, + "after (\"spot\") -> \"checked\""); + ensure_contains("cyclic dependencies", threw, + "after (\"Mary\", \"checked\") -> \"spot\""); + button.listen("yellow", + boost::bind(&Collect::add, boost::ref(collector), "yellow", _1), + make(list_of("checked"))); + button.listen("shoelaces", + boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), + make(list_of("checked"))); + button.post(3); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + collector.clear(); + threw.clear(); + try + { + button.listen("of", + boost::bind(&Collect::add, boost::ref(collector), "of", _1), + make(list_of("shoelaces")), + make(list_of("yellow"))); + } + catch (const LLEventPump::OrderChange& e) + { + threw = e.what(); +// std::cout << "Caught: " << e.what() << '\n'; + } + // Same remarks about the specific wording of the exception. Just + // ensure that it contains enough information to clarify the + // problem and what must be done to resolve it. + ensure_contains("LLEventPump type", threw, typeid(button).name()); + ensure_contains("LLEventPump name", threw, "'button'"); + ensure_contains("new listener name", threw, "'of'"); + ensure_contains("prev listener name", threw, "'yellow'"); + ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces"); + ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow"); + button.post(4); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + } + + template<> template<> + void events_object::test<8>() + { + set_test_name("tweaked and untweaked LLEventPump instance names"); + { // nested scope + // Hand-instantiate an LLEventStream... + LLEventStream bob("bob"); + bool threw = false; + try + { + // then another with a duplicate name. + LLEventStream bob2("bob"); + } + catch (const LLEventPump::DupPumpName& /*e*/) + { + threw = true; +// std::cout << "Caught: " << e.what() << '\n'; + } + ensure("Caught DupPumpName", threw); + } // delete first 'bob' + LLEventStream bob("bob"); // should work, previous one unregistered + LLEventStream bob1("bob", true); // allowed to tweak name + ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1"); + std::vector< boost::shared_ptr > streams; + for (int i = 2; i <= 10; ++i) + { + streams.push_back(boost::shared_ptr(new LLEventStream("bob", true))); + } + ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10"); + } + + // Define a function that accepts an LLListenerOrPumpName + void eventSource(const LLListenerOrPumpName& listener) + { + // Pretend that some time has elapsed. Call listener immediately. + listener(17); + } + + template<> template<> + void events_object::test<9>() + { + set_test_name("LLListenerOrPumpName"); + // Passing a boost::bind() expression to LLListenerOrPumpName + listener0.reset(0); + eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1)); + check_listener("got by listener", listener0, 17); + // Passing a string LLEventPump name to LLListenerOrPumpName + listener0.reset(0); + LLEventStream random("random"); + random.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + eventSource("random"); + check_listener("got by pump name", listener0, 17); + bool threw = false; + try + { + LLListenerOrPumpName empty; + empty(17); + } + catch (const LLListenerOrPumpName::Empty&) + { + threw = true; + } + ensure("threw Empty", threw); + } + + class TempListener: public Listener + { + public: + TempListener(const std::string& name, bool& liveFlag): + Listener(name), + mLiveFlag(liveFlag) + { + mLiveFlag = true; + } + + virtual ~TempListener() + { + mLiveFlag = false; + } + + private: + bool& mLiveFlag; + }; + + template<> template<> + void events_object::test<10>() + { + set_test_name("listen(boost::bind(...TempListener...))"); + // listen() can't do anything about a plain TempListener instance: + // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempListener tempListener("temp", live); + ensure("TempListener constructed", live); + connection = heaptest.listen(tempListener.getName(), + boost::bind(&Listener::call, + boost::ref(tempListener), + _1)); + heaptest.post(1); + check_listener("received", tempListener, 1); + } // presumably this will make newListener go away? + // verify that + ensure("TempListener destroyed", ! live); + // This is the case against which we can't defend. Don't even try to + // post to heaptest -- that would engage Undefined Behavior. + // Cautiously inspect connection... + ensure("misleadingly connected", connection.connected()); + // then disconnect by hand. + heaptest.stopListening("temp"); + } + + template<> template<> + void events_object::test<11>() + { + set_test_name("listen(boost::bind(...weak_ptr...))"); + // listen() detecting weak_ptr in boost::bind() object + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + ensure("default state", ! connection.connected()); + { + boost::shared_ptr newListener(new TempListener("heap", live)); + newListener->reset(); + ensure("TempListener constructed", live); + connection = heaptest.listen(newListener->getName(), + boost::bind(&Listener::call, weaken(newListener), _1)); + ensure("new connection", connection.connected()); + heaptest.post(1); + check_listener("received", *newListener, 1); + } // presumably this will make newListener go away? + // verify that + ensure("TempListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); + } + + template<> template<> + void events_object::test<12>() + { + set_test_name("listen(boost::bind(...shared_ptr...))"); +/*==========================================================================*| + // DISABLED because I've made this case produce a compile error. + // Following the error leads the disappointed dev to a comment + // instructing her to use the weaken() function to bind a weak_ptr + // instead of binding a shared_ptr, and explaining why. I know of + // no way to use TUT to code a repeatable test in which the expected + // outcome is a compile error. The interested reader is invited to + // uncomment this block and build to see for herself. + + // listen() detecting shared_ptr in boost::bind() object + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + std::string listenerName("heap"); + ensure("default state", ! connection.connected()); + { + boost::shared_ptr newListener(new TempListener(listenerName, live)); + ensure_equals("use_count", newListener.use_count(), 1); + newListener->reset(); + ensure("TempListener constructed", live); + connection = heaptest.listen(newListener->getName(), + boost::bind(&Listener::call, newListener, _1)); + ensure("new connection", connection.connected()); + ensure_equals("use_count", newListener.use_count(), 2); + heaptest.post(1); + check_listener("received", *newListener, 1); + } // this should make newListener go away... + // Unfortunately, the fact that we've bound a shared_ptr by value into + // our LLEventPump means that copy will keep the referenced object alive. + ensure("TempListener still alive", live); + ensure("still connected", connection.connected()); + // disconnecting explicitly should delete the TempListener... + heaptest.stopListening(listenerName); +#if 0 // however, in my experience, it does not. I don't know why not. + // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2 + // library, stated on the boost-users mailing list: + // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html + // "It will get destroyed eventually. The signal cleans up its slot + // list little by little during connect/invoke. It doesn't immediately + // remove disconnected slots from the slot list since other threads + // might be using the same slot list concurrently. It might be + // possible to make it immediately reset the shared_ptr owning the + // slot though, leaving an empty shared_ptr in the slot list, since + // that wouldn't invalidate any iterators." + ensure("TempListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); +#endif // 0 + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); +|*==========================================================================*/ + } + + class TempTrackableListener: public TempListener, public LLEventTrackable + { + public: + TempTrackableListener(const std::string& name, bool& liveFlag): + TempListener(name, liveFlag) + {} + }; + + template<> template<> + void events_object::test<13>() + { + set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempTrackableListener tempListener("temp", live); + ensure("TempTrackableListener constructed", live); + connection = heaptest.listen(tempListener.getName(), + boost::bind(&TempTrackableListener::call, + boost::ref(tempListener), _1)); + heaptest.post(1); + check_listener("received", tempListener, 1); + } // presumably this will make tempListener go away? + // verify that + ensure("TempTrackableListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); + } + + template<> template<> + void events_object::test<14>() + { + set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + TempTrackableListener* newListener(new TempTrackableListener("temp", live)); + ensure("TempTrackableListener constructed", live); + connection = heaptest.listen(newListener->getName(), + boost::bind(&TempTrackableListener::call, + newListener, _1)); + heaptest.post(1); + check_listener("received", *newListener, 1); + // explicitly destroy newListener + delete newListener; + } + // verify that + ensure("TempTrackableListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); + } + + class TempSharedListener: public TempListener, + public boost::enable_shared_from_this + { + public: + TempSharedListener(const std::string& name, bool& liveFlag): + TempListener(name, liveFlag) + {} + }; + + template<> template<> + void events_object::test<15>() + { + set_test_name("listen(boost::bind(...TempSharedListener ref...))"); +#if 0 + bool live = false; + LLEventPump& heaptest(pumps.obtain("heaptest")); + LLBoundListener connection; + { + // We MUST have at least one shared_ptr to an + // enable_shared_from_this subclass object before + // shared_from_this() can work. + boost::shared_ptr + tempListener(new TempSharedListener("temp", live)); + ensure("TempSharedListener constructed", live); + // However, we're not passing either the shared_ptr or its + // corresponding weak_ptr -- instead, we're passing a reference to + // the TempSharedListener. +/*==========================================================================*| + std::cout << "Capturing const ref" << std::endl; + const boost::enable_shared_from_this& cref(*tempListener); + std::cout << "Capturing const ptr" << std::endl; + const boost::enable_shared_from_this* cp(&cref); + std::cout << "Capturing non-const ptr" << std::endl; + boost::enable_shared_from_this* p(const_cast*>(cp)); + std::cout << "Capturing shared_from_this()" << std::endl; + boost::shared_ptr sp(p->shared_from_this()); + std::cout << "Capturing weak_ptr" << std::endl; + boost::weak_ptr wp(weaken(sp)); + std::cout << "Binding weak_ptr" << std::endl; +|*==========================================================================*/ + connection = heaptest.listen(tempListener->getName(), + boost::bind(&TempSharedListener::call, *tempListener, _1)); + heaptest.post(1); + check_listener("received", *tempListener, 1); + } // presumably this will make tempListener go away? + // verify that + ensure("TempSharedListener destroyed", ! live); + ensure("implicit disconnect", ! connection.connected()); + // now just make sure we don't blow up trying to access a freed object! + heaptest.post(2); +#endif // 0 + } +} // namespace tut diff --git a/install.xml b/install.xml index ed108aacd8..24cbd37575 100644 --- a/install.xml +++ b/install.xml @@ -207,30 +207,30 @@ darwin md5sum - 279834a12a0ed4531fd602594eac8509 + 081ef195a856c708cc473c4421b4b290 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-darwin-20080812.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090223.tar.bz2 linux md5sum - b9a943052e5525da5417d6f471d70bc5 + b516a8576ecad0f957db7fc2cd972aac url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-linux-20080812.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090223a.tar.bz2 linux64 md5sum - b97ae0855e77cc25b37ca63df093bb9b + 6db62bb7f141b3a1b3107e1f9aad0eb0 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.0-linux64-20080909.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090223a.tar.bz2 windows md5sum - d2b2ad9b46b9981c2a6be7c912bd17b4 + 3b56fe9e8d2975c612639d0a5370ffe7 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20080723.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090225.tar.bz2 -- cgit v1.3 From dc934629919bdcaea72c78e5291263914fb958ec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2009 20:05:46 +0000 Subject: svn merge -r113003:119136 svn+ssh://svn.lindenlab.com/svn/linden/branches/login-api/login-api-2 svn+ssh://svn.lindenlab.com/svn/linden/branches/login-api/login-api-3 --- indra/CMakeLists.txt | 1 + indra/cmake/LLAddBuildTest.cmake | 7 +- indra/cmake/LLLogin.cmake | 7 + indra/cmake/Pth.cmake | 21 + indra/llcommon/CMakeLists.txt | 6 + indra/llcommon/lleventcoro.cpp | 118 +++++ indra/llcommon/lleventcoro.h | 542 +++++++++++++++++++++ indra/llcommon/lleventfilter.cpp | 149 ++++++ indra/llcommon/lleventfilter.h | 186 +++++++ indra/llcommon/llevents.cpp | 7 + indra/llcommon/llevents.h | 122 +++-- indra/llcommon/llsdutil.cpp | 263 ++++++++++ indra/llcommon/llsdutil.h | 55 +++ indra/llcommon/tests/listener.h | 139 ++++++ indra/llcommon/tests/lleventfilter_test.cpp | 276 +++++++++++ indra/llcommon/tests/wrapllerrs.h | 56 +++ indra/llmessage/CMakeLists.txt | 3 + indra/llmessage/llares.cpp | 8 +- indra/llmessage/llares.h | 12 + indra/llmessage/llareslistener.cpp | 108 ++++ indra/llmessage/llareslistener.h | 47 ++ indra/llmessage/tests/llareslistener_test.cpp | 194 ++++++++ indra/llmessage/tests/test_llsdmessage_peer.py | 30 +- indra/llmessage/tests/testrunner.py | 53 ++ indra/newview/CMakeLists.txt | 13 +- indra/newview/llappviewer.cpp | 4 - indra/newview/llappviewer.h | 4 - indra/newview/llclassifiedinfo.cpp | 36 +- indra/newview/llclassifiedinfo.h | 3 +- indra/newview/lleventinfo.cpp | 36 +- indra/newview/lleventinfo.h | 3 +- indra/newview/lleventnotifier.cpp | 79 ++- indra/newview/lleventnotifier.h | 5 +- indra/newview/llfloatertos.cpp | 40 +- indra/newview/llfloatertos.h | 12 +- indra/newview/llinventorymodel.cpp | 205 ++++---- indra/newview/llinventorymodel.h | 6 +- indra/newview/lllogininstance.cpp | 532 ++++++++++++++++++++ indra/newview/lllogininstance.h | 95 ++++ indra/newview/llpanellogin.cpp | 2 +- indra/newview/llstartup.h | 6 - indra/newview/llviewermenu.cpp | 1 - indra/newview/llviewernetwork.cpp | 6 + indra/newview/llviewernetwork.h | 4 + indra/newview/llxmlrpclistener.cpp | 494 +++++++++++++++++++ indra/newview/llxmlrpclistener.h | 35 ++ indra/newview/llxmlrpctransaction.cpp | 13 + indra/newview/tests/llcapabilitylistener_test.cpp | 36 +- indra/newview/tests/llxmlrpclistener_test.cpp | 230 +++++++++ indra/newview/tests/test_llxmlrpc_peer.py | 59 +++ indra/test/llevents_tut.cpp | 133 +---- indra/test/llsdutil_tut.cpp | 180 ++++++- indra/test/lltut.cpp | 12 +- indra/test/lltut.h | 3 + indra/test/test.cpp | 19 +- indra/viewer_components/CMakeLists.txt | 1 + indra/viewer_components/login/CMakeLists.txt | 43 ++ indra/viewer_components/login/lllogin.cpp | 383 +++++++++++++++ indra/viewer_components/login/lllogin.h | 133 +++++ .../viewer_components/login/tests/lllogin_test.cpp | 382 +++++++++++++++ install.xml | 63 ++- 61 files changed, 5218 insertions(+), 503 deletions(-) create mode 100644 indra/cmake/LLLogin.cmake create mode 100644 indra/cmake/Pth.cmake create mode 100644 indra/llcommon/lleventcoro.cpp create mode 100644 indra/llcommon/lleventcoro.h create mode 100644 indra/llcommon/lleventfilter.cpp create mode 100644 indra/llcommon/lleventfilter.h create mode 100644 indra/llcommon/tests/listener.h create mode 100644 indra/llcommon/tests/lleventfilter_test.cpp create mode 100644 indra/llcommon/tests/wrapllerrs.h create mode 100644 indra/llmessage/llareslistener.cpp create mode 100644 indra/llmessage/llareslistener.h create mode 100644 indra/llmessage/tests/llareslistener_test.cpp create mode 100644 indra/llmessage/tests/testrunner.py create mode 100644 indra/newview/lllogininstance.cpp create mode 100644 indra/newview/lllogininstance.h create mode 100644 indra/newview/llxmlrpclistener.cpp create mode 100644 indra/newview/llxmlrpclistener.h create mode 100644 indra/newview/tests/llxmlrpclistener_test.cpp create mode 100644 indra/newview/tests/test_llxmlrpc_peer.py create mode 100644 indra/viewer_components/CMakeLists.txt create mode 100644 indra/viewer_components/login/CMakeLists.txt create mode 100644 indra/viewer_components/login/lllogin.cpp create mode 100644 indra/viewer_components/login/lllogin.h create mode 100644 indra/viewer_components/login/tests/lllogin_test.cpp (limited to 'indra/llmessage') diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index c285bcae4b..e8e05f727b 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -64,6 +64,7 @@ add_custom_target(viewer) if (VIEWER) add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger) add_subdirectory(${LIBS_OPEN_PREFIX}llui) + add_subdirectory(${LIBS_OPEN_PREFIX}viewer_components) if (LINUX) add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index b19eebe1fe..6eeff45afe 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -1,6 +1,7 @@ # -*- cmake -*- INCLUDE(APR) +INCLUDE(Pth) INCLUDE(LLMath) MACRO(ADD_BUILD_TEST_NO_COMMON name parent) @@ -36,6 +37,7 @@ MACRO(ADD_BUILD_TEST name parent) ${APR_LIBRARIES} ${PTHREAD_LIBRARY} ${WINDOWS_LIBRARIES} + ${PTH_LIBRARIES} ) SET(basic_source_files ${name}.cpp @@ -89,10 +91,13 @@ MACRO(ADD_BUILD_TEST_INTERNAL name parent libraries source_files) GET_TARGET_PROPERTY(TEST_EXE ${name}_test LOCATION) SET(TEST_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${name}_test_ok.txt) + SET(run_needs ${name}_test) + IF ("${wrapper}" STREQUAL "") SET(TEST_CMD ${TEST_EXE} --touch=${TEST_OUTPUT} --sourcedir=${CMAKE_CURRENT_SOURCE_DIR}) ELSE ("${wrapper}" STREQUAL "") SET(TEST_CMD ${PYTHON_EXECUTABLE} ${wrapper} ${TEST_EXE} --touch=${TEST_OUTPUT} --sourcedir=${CMAKE_CURRENT_SOURCE_DIR}) + SET(run_needs ${run_needs} ${wrapper}) ENDIF ("${wrapper}" STREQUAL "") #MESSAGE(STATUS "ADD_BUILD_TEST_INTERNAL ${name} test_cmd = ${TEST_CMD}") @@ -107,7 +112,7 @@ MACRO(ADD_BUILD_TEST_INTERNAL name parent libraries source_files) ADD_CUSTOM_COMMAND( OUTPUT ${TEST_OUTPUT} COMMAND ${TEST_SCRIPT_CMD} - DEPENDS ${name}_test + DEPENDS ${run_needs} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/indra/cmake/LLLogin.cmake b/indra/cmake/LLLogin.cmake new file mode 100644 index 0000000000..47d171876a --- /dev/null +++ b/indra/cmake/LLLogin.cmake @@ -0,0 +1,7 @@ +# -*- cmake -*- + +set(LLLOGIN_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/viewer_components/login + ) + +set(LLLOGIN_LIBRARIES lllogin) diff --git a/indra/cmake/Pth.cmake b/indra/cmake/Pth.cmake new file mode 100644 index 0000000000..a28f6ec696 --- /dev/null +++ b/indra/cmake/Pth.cmake @@ -0,0 +1,21 @@ +# -*- cmake -*- +include(Prebuilt) + +set(PTH_FIND_QUIETLY ON) +set(PTH_FIND_REQUIRED ON) + +if (STANDALONE) +# ?? How would I construct FindPTH.cmake? This file was cloned from +# CURL.cmake, which uses include(FindCURL), but there's no FindCURL.cmake? +# include(FindPTH) +else (STANDALONE) + # This library is only needed to support Boost.Coroutine, and only on Mac. + if (DARWIN) + use_prebuilt_binary(pth) + set(PTH_LIBRARIES pth) + set(PTH_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include) + else (DARWIN) + set(PTH_LIBRARIES) + set(PTH_INCLUDE_DIRS) + endif (DARWIN) +endif (STANDALONE) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 694f3d5de8..d3d75f78df 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -33,6 +33,8 @@ set(llcommon_SOURCE_FILES llerror.cpp llerrorthread.cpp llevent.cpp + lleventcoro.cpp + lleventfilter.cpp llevents.cpp llfasttimer.cpp llfile.cpp @@ -118,6 +120,8 @@ set(llcommon_HEADER_FILES llerrorlegacy.h llerrorthread.h llevent.h + lleventcoro.h + lleventfilter.h llevents.h lleventemitter.h llextendedstatus.h @@ -223,3 +227,5 @@ target_link_libraries( ) ADD_BUILD_TEST(lllazy llcommon) +ADD_BUILD_TEST(lleventfilter llcommon) +ADD_BUILD_TEST(coroutine llcommon) diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp new file mode 100644 index 0000000000..cea5a1eda3 --- /dev/null +++ b/indra/llcommon/lleventcoro.cpp @@ -0,0 +1,118 @@ +/** + * @file lleventcoro.cpp + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Implementation for lleventcoro. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventcoro.h" +// STL headers +#include +// std headers +// external library headers +// other Linden headers +#include "llsdserialize.h" +#include "llerror.h" + +std::string LLEventDetail::listenerNameForCoro(const void* self) +{ + typedef std::map MapType; + static MapType memo; + MapType::const_iterator found = memo.find(self); + if (found != memo.end()) + { + // this coroutine instance has called us before, reuse same name + return found->second; + } + // this is the first time we've been called for this coroutine instance + std::string name(LLEventPump::inventName("coro")); + memo[self] = name; + return name; +} + +void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +{ + if (rawPath.isUndefined()) + { + // no-op case + return; + } + + // Arrange to treat rawPath uniformly as an array. If it's not already an + // array, store it as the only entry in one. + LLSD path; + if (rawPath.isArray()) + { + path = rawPath; + } + else + { + path.append(rawPath); + } + + // Need to indicate a current destination -- but that current destination + // needs to change as we step through the path array. Where normally we'd + // use an LLSD& to capture a subscripted LLSD lvalue, this time we must + // instead use a pointer -- since it must be reassigned. + LLSD* pdest = &dest; + + // Now loop through that array + for (LLSD::Integer i = 0; i < path.size(); ++i) + { + if (path[i].isString()) + { + // *pdest is an LLSD map + pdest = &((*pdest)[path[i].asString()]); + } + else if (path[i].isInteger()) + { + // *pdest is an LLSD array + pdest = &((*pdest)[path[i].asInteger()]); + } + else + { + // What do we do with Real or Array or Map or ...? + // As it's a coder error -- not a user error -- rub the coder's + // face in it so it gets fixed. + LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value + << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL; + } + } + + // Here *pdest is where we should store value. + *pdest = value; +} + +LLSD errorException(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), instead of + // returning it, deliver it via exception. + if (result.second) + { + throw LLErrorEvent(desc, result.first); + } + // That way, our caller knows a simple return must be from the reply + // pump (pump 0). + return result.first; +} + +LLSD errorLog(const LLEventWithID& result, const std::string& desc) +{ + // If the result arrived on the error pump (pump 1), log it as a fatal + // error. + if (result.second) + { + LL_ERRS("errorLog") << desc << ":" << std::endl; + LLSDSerialize::toPrettyXML(result.first, LL_CONT); + LL_CONT << LL_ENDL; + } + // A simple return must therefore be from the reply pump (pump 0). + return result.first; +} diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h new file mode 100644 index 0000000000..7232d1780f --- /dev/null +++ b/indra/llcommon/lleventcoro.h @@ -0,0 +1,542 @@ +/** + * @file lleventcoro.h + * @author Nat Goodspeed + * @date 2009-04-29 + * @brief Utilities to interface between coroutines and events. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTCORO_H) +#define LL_LLEVENTCORO_H + +#include +#include +#include +#include +#include +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a const LLEventPumpOrPumpName& and you can accept either an + * LLEventPump& or its string name. For a single parameter that could + * be either, it's not hard to overload the function -- but as soon as you + * want to accept two such parameters, this is cheaper than four overloads. + */ +class LLEventPumpOrPumpName +{ +public: + /// Pass an actual LLEventPump& + LLEventPumpOrPumpName(LLEventPump& pump): + mPump(pump) + {} + /// Pass the string name of an LLEventPump + LLEventPumpOrPumpName(const std::string& pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Pass string constant name of an LLEventPump. This override must be + /// explicit, since otherwise passing const char* to a function + /// accepting const LLEventPumpOrPumpName& would require two + /// different implicit conversions: const char* -> const + /// std::string& -> const LLEventPumpOrPumpName&. + LLEventPumpOrPumpName(const char* pumpname): + mPump(LLEventPumps::instance().obtain(pumpname)) + {} + /// Unspecified: "I choose not to identify an LLEventPump." + LLEventPumpOrPumpName() {} + operator LLEventPump& () const { return *mPump; } + LLEventPump& getPump() const { return *mPump; } + operator bool() const { return mPump; } + bool operator!() const { return ! mPump; } + +private: + boost::optional mPump; +}; + +/// This is an adapter for a signature like void LISTENER(const LLSD&), which +/// isn't a valid LLEventPump listener: such listeners should return bool. +template +class LLVoidListener +{ +public: + LLVoidListener(const LISTENER& listener): + mListener(listener) + {} + bool operator()(const LLSD& event) + { + mListener(event); + // don't swallow the event, let other listeners see it + return false; + } +private: + LISTENER mListener; +}; + +/// LLVoidListener helper function to infer the type of the LISTENER +template +LLVoidListener voidlistener(const LISTENER& listener) +{ + return LLVoidListener(listener); +} + +namespace LLEventDetail +{ + /** + * waitForEventOn() permits a coroutine to temporarily listen on an + * LLEventPump any number of times. We don't really want to have to ask + * the caller to label each such call with a distinct string; the whole + * point of waitForEventOn() is to present a nice sequential interface to + * the underlying LLEventPump-with-named-listeners machinery. So we'll use + * LLEventPump::inventName() to generate a distinct name for each + * temporary listener. On the other hand, because a given coroutine might + * call waitForEventOn() any number of times, we don't really want to + * consume an arbitrary number of generated inventName()s: that namespace, + * though large, is nonetheless finite. So we memoize an invented name for + * each distinct coroutine instance (each different 'self' object). We + * can't know the type of 'self', because it depends on the coroutine + * body's signature. So we cast its address to void*, looking for distinct + * pointer values. Yes, that means that an early coroutine could cache a + * value here, then be destroyed, only to be supplanted by a later + * coroutine (of the same or different type), and we'll end up + * "recognizing" the second one and reusing the listener name -- but + * that's okay, since it won't collide with any listener name used by the + * earlier coroutine since that earlier coroutine no longer exists. + */ + std::string listenerNameForCoro(const void* self); + + /** + * Implement behavior described for postAndWait()'s @a replyPumpNamePath + * parameter: + * + * * If path.isUndefined(), do nothing. + * * If path.isString(), @a dest is an LLSD map: store @a value + * into dest[path.asString()]. + * * If path.isInteger(), @a dest is an LLSD array: store @a + * value into dest[path.asInteger()]. + * * If path.isArray(), iteratively apply the rules above to step + * down through the structure of @a dest. The last array entry in @a + * path specifies the entry in the lowest-level structure in @a dest + * into which to store @a value. + * + * @note + * In the degenerate case in which @a path is an empty array, @a dest will + * @em become @a value rather than @em containing it. + */ + void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); +} // namespace LLEventDetail + +/** + * Post specified LLSD event on the specified LLEventPump, then wait for a + * response on specified other LLEventPump. This is more than mere + * convenience: the difference between this function and the sequence + * @code + * requestPump.post(myEvent); + * LLSD reply = waitForEventOn(self, replyPump); + * @endcode + * is that the sequence above fails if the reply is posted immediately on + * @a replyPump, that is, before requestPump.post() returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until requestPump.post() returns and @c waitForEventOn() is + * entered. Therefore, the coroutine completely misses an immediate reply + * event, making it wait indefinitely. + * + * By contrast, postAndWait() listens on the @a replyPump @em before posting + * the specified LLSD event on the specified @a requestPump. + * + * @param self The @c self object passed into a coroutine + * @param event LLSD data to be posted on @a requestPump + * @param requestPump an LLEventPump on which to post @a event. Pass either + * the LLEventPump& or its string name. However, if you pass a + * default-constructed @c LLEventPumpOrPumpName, we skip the post() call. + * @param replyPump an LLEventPump on which postAndWait() will listen for a + * reply. Pass either the LLEventPump& or its string name. The calling + * coroutine will wait until that reply arrives. (If you're concerned about a + * reply that might not arrive, please see also LLEventTimeout.) + * @param replyPumpNamePath specifies the location within @a event in which to + * store replyPump.getName(). This is a strictly optional convenience + * feature; obviously you can store the name in @a event "by hand" if desired. + * @a replyPumpNamePath can be specified in any of four forms: + * * @c isUndefined() (default-constructed LLSD object): do nothing. This is + * the default behavior if you omit @a replyPumpNamePath. + * * @c isInteger(): @a event is an array. Store replyPump.getName() + * in event[replyPumpNamePath.asInteger()]. + * * @c isString(): @a event is a map. Store replyPump.getName() in + * event[replyPumpNamePath.asString()]. + * * @c isArray(): @a event has several levels of structure, e.g. map of + * maps, array of arrays, array of maps, map of arrays, ... Store + * replyPump.getName() in + * event[replyPumpNamePath[0]][replyPumpNamePath[1]]... In other + * words, examine each array entry in @a replyPumpNamePath in turn. If it's an + * LLSD::String, the current level of @a event is a map; step down to + * that map entry. If it's an LLSD::Integer, the current level of @a + * event is an array; step down to that array entry. The last array entry in + * @a replyPumpNamePath specifies the entry in the lowest-level structure in + * @a event into which to store replyPump.getName(). + */ +template +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ + // declare the future + boost::coroutines::future future(self); + // make a callback that will assign a value to the future, and listen on + // the specified LLEventPump with that callback + std::string listenerName(LLEventDetail::listenerNameForCoro(&self)); + LLTempBoundListener connection( + replyPump.getPump().listen(listenerName, + voidlistener(boost::coroutines::make_callback(future)))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If replyPumpNamePath is non-empty, store the replyPump name in the + // request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " about to wait on LLEventPump " << replyPump.getPump().getName() + << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLSD value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; +} + +/// Wait for the next event on the specified LLEventPump. Pass either the +/// LLEventPump& or its string name. +template +LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump) +{ + // This is now a convenience wrapper for postAndWait(). + return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump); +} + +/// return type for two-pump variant of waitForEventOn() +typedef std::pair LLEventWithID; + +namespace LLEventDetail +{ + /** + * This helper is specifically for the two-pump version of waitForEventOn(). + * We use a single future object, but we want to listen on two pumps with it. + * Since we must still adapt from (the callable constructed by) + * boost::coroutines::make_callback() (void return) to provide an event + * listener (bool return), we've adapted LLVoidListener for the purpose. The + * basic idea is that we construct a distinct instance of WaitForEventOnHelper + * -- binding different instance data -- for each of the pumps. Then, when a + * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine + * that LLSD with its discriminator to feed the future object. + */ + template + class WaitForEventOnHelper + { + public: + WaitForEventOnHelper(const LISTENER& listener, int discriminator): + mListener(listener), + mDiscrim(discriminator) + {} + // this signature is required for an LLEventPump listener + bool operator()(const LLSD& event) + { + // our future object is defined to accept LLEventWithID + mListener(LLEventWithID(event, mDiscrim)); + // don't swallow the event, let other listeners see it + return false; + } + private: + LISTENER mListener; + const int mDiscrim; + }; + + /// WaitForEventOnHelper type-inference helper + template + WaitForEventOnHelper wfeoh(const LISTENER& listener, int discriminator) + { + return WaitForEventOnHelper(listener, discriminator); + } +} // namespace LLEventDetail + +/** + * This function waits for a reply on either of two specified LLEventPumps. + * Otherwise, it closely resembles postAndWait(); please see the documentation + * for that function for detailed parameter info. + * + * While we could have implemented the single-pump variant in terms of this + * one, there's enough added complexity here to make it worthwhile to give the + * single-pump variant its own straightforward implementation. Conversely, + * though we could use preprocessor logic to generate n-pump overloads up to + * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump + * overload exists because certain event APIs are defined in terms of a reply + * LLEventPump and an error LLEventPump. + * + * The LLEventWithID return value provides not only the received event, but + * the index of the pump on which it arrived (0 or 1). + * + * @note + * I'd have preferred to overload the name postAndWait() for both signatures. + * But consider the following ambiguous call: + * @code + * postAndWait(self, LLSD(), requestPump, replyPump, "someString"); + * @endcode + * "someString" could be converted to either LLSD (@a replyPumpNamePath for + * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump + * function). + * + * It seems less burdensome to write postAndWait2() than to write either + * LLSD("someString") or LLEventOrPumpName("someString"). + */ +template +LLEventWithID postAndWait2(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump0, + const LLEventPumpOrPumpName& replyPump1, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) +{ + // declare the future + boost::coroutines::future future(self); + // either callback will assign a value to this future; listen on + // each specified LLEventPump with a callback + std::string name(LLEventDetail::listenerNameForCoro(&self)); + LLTempBoundListener connection0( + replyPump0.getPump().listen(name + "a", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 0))); + LLTempBoundListener connection1( + replyPump1.getPump().listen(name + "b", + LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 1))); + // skip the "post" part if requestPump is default-constructed + if (requestPump) + { + // If either replyPumpNamePath is non-empty, store the corresponding + // replyPump name in the request event. + LLSD modevent(event); + LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath, + replyPump0.getPump().getName()); + LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath, + replyPump1.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name + << " about to wait on LLEventPumps " << replyPump0.getPump().getName() + << ", " << replyPump1.getPump().getName() << LL_ENDL; + // trying to dereference ("resolve") the future makes us wait for it + LLEventWithID value(*future); + LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name + << " resuming with (" << value.first << ", " << value.second << ")" + << LL_ENDL; + // returning should disconnect both connections + return value; +} + +/** + * Wait for the next event on either of two specified LLEventPumps. + */ +template +LLEventWithID +waitForEventOn(SELF& self, + const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +{ + // This is now a convenience wrapper for postAndWait2(). + return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1); +} + +/** + * Helper for the two-pump variant of waitForEventOn(), e.g.: + * + * @code + * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump), + * "error response from login.cgi"); + * @endcode + * + * Examines an LLEventWithID, assuming that the second pump (pump 1) is + * listening for an error indication. If the incoming data arrived on pump 1, + * throw an LLErrorEvent exception. If the incoming data arrived on pump 0, + * just return it. Since a normal return can only be from pump 0, we no longer + * need the LLEventWithID's discriminator int; we can just return the LLSD. + * + * @note I'm not worried about introducing the (fairly generic) name + * errorException() into global namespace, because how many other overloads of + * the same name are going to accept an LLEventWithID parameter? + */ +LLSD errorException(const LLEventWithID& result, const std::string& desc); + +/** + * Exception thrown by errorException(). We don't call this LLEventError + * because it's not an error in event processing: rather, this exception + * announces an event that bears error information (for some other API). + */ +class LLErrorEvent: public std::runtime_error +{ +public: + LLErrorEvent(const std::string& what, const LLSD& data): + std::runtime_error(what), + mData(data) + {} + virtual ~LLErrorEvent() throw() {} + + LLSD getData() const { return mData; } + +private: + LLSD mData; +}; + +/** + * Like errorException(), save that this trips a fatal error using LL_ERRS + * rather than throwing an exception. + */ +LLSD errorLog(const LLEventWithID& result, const std::string& desc); + +/** + * Certain event APIs require the name of an LLEventPump on which they should + * post results. While it works to invent a distinct name and let + * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton," + * in a certain sense it's more robust to instantiate a local LLEventPump and + * provide its name instead. This class packages the following idiom: + * + * 1. Instantiate a local LLCoroEventPump, with an optional name prefix. + * 2. Provide its actual name to the event API in question as the name of the + * reply LLEventPump. + * 3. Initiate the request to the event API. + * 4. Call your LLEventTempStream's wait() method to wait for the reply. + * 5. Let the LLCoroEventPump go out of scope. + */ +class LLCoroEventPump +{ +public: + LLCoroEventPump(const std::string& name="coro"): + mPump(name, true) // allow tweaking the pump instance name + {} + /// It's typical to request the LLEventPump name to direct an event API to + /// send its response to this pump. + std::string getName() const { return mPump.getName(); } + /// Less typically, we'd request the pump itself for some reason. + LLEventPump& getPump() { return mPump; } + + /** + * Wait for an event on this LLEventPump. + * + * @note + * The other major usage pattern we considered was to bind @c self at + * LLCoroEventPump construction time, which would avoid passing the + * parameter to each wait() call. But if we were going to bind @c self as + * a class member, we'd need to specify a class template parameter + * indicating its type. The big advantage of passing it to the wait() call + * is that the type can be implicit. + */ + template + LLSD wait(SELF& self) + { + return waitForEventOn(self, mPump); + } + + template + LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPumpNamePath=LLSD()) + { + return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath); + } + +private: + LLEventStream mPump; +}; + +/** + * Other event APIs require the names of two different LLEventPumps: one for + * success response, the other for error response. Extend LLCoroEventPump + * for the two-pump use case. + */ +class LLCoroEventPumps +{ +public: + LLCoroEventPumps(const std::string& name="coro", + const std::string& suff0="Reply", + const std::string& suff1="Error"): + mPump0(name + suff0, true), // allow tweaking the pump instance name + mPump1(name + suff1, true) + {} + /// request pump 0's name + std::string getName0() const { return mPump0.getName(); } + /// request pump 1's name + std::string getName1() const { return mPump1.getName(); } + /// request both names + std::pair getNames() const + { + return std::pair(mPump0.getName(), mPump1.getName()); + } + + /// request pump 0 + LLEventPump& getPump0() { return mPump0; } + /// request pump 1 + LLEventPump& getPump1() { return mPump1; } + + /// waitForEventOn(self, either of our two LLEventPumps) + template + LLEventWithID wait(SELF& self) + { + return waitForEventOn(self, mPump0, mPump1); + } + + /// errorException(wait(self)) + template + LLSD waitWithException(SELF& self) + { + return errorException(wait(self), std::string("Error event on ") + getName1()); + } + + /// errorLog(wait(self)) + template + LLSD waitWithLog(SELF& self) + { + return errorLog(wait(self), std::string("Error event on ") + getName1()); + } + + template + LLEventWithID postAndWait(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return postAndWait2(self, event, requestPump, mPump0, mPump1, + replyPump0NamePath, replyPump1NamePath); + } + + template + LLSD postAndWaitWithException(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorException(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + + template + LLSD postAndWaitWithLog(SELF& self, const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLSD& replyPump0NamePath=LLSD(), + const LLSD& replyPump1NamePath=LLSD()) + { + return errorLog(postAndWait(self, event, requestPump, + replyPump0NamePath, replyPump1NamePath), + std::string("Error event on ") + getName1()); + } + +private: + LLEventStream mPump0, mPump1; +}; + +#endif /* ! defined(LL_LLEVENTCORO_H) */ diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp new file mode 100644 index 0000000000..74133781be --- /dev/null +++ b/indra/llcommon/lleventfilter.cpp @@ -0,0 +1,149 @@ +/** + * @file lleventfilter.cpp + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Implementation for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +#include +// other Linden headers +#include "llerror.h" // LL_ERRS +#include "llsdutil.h" // llsd_matches() + +LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): + LLEventStream(name, tweak) +{ + source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)); +} + +LLEventMatching::LLEventMatching(const LLSD& pattern): + LLEventFilter("matching"), + mPattern(pattern) +{ +} + +LLEventMatching::LLEventMatching(LLEventPump& source, const LLSD& pattern): + LLEventFilter(source, "matching"), + mPattern(pattern) +{ +} + +bool LLEventMatching::post(const LLSD& event) +{ + if (! llsd_matches(mPattern, event).empty()) + return false; + + return LLEventStream::post(event); +} + +LLEventTimeoutBase::LLEventTimeoutBase(): + LLEventFilter("timeout") +{ +} + +LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source): + LLEventFilter(source, "timeout") +{ +} + +void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) +{ + setCountdown(seconds); + mAction = action; + if (! mMainloop.connected()) + { + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); + } +} + +class ErrorAfter +{ +public: + ErrorAfter(const std::string& message): mMessage(message) {} + + void operator()() + { + LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL; + } + +private: + std::string mMessage; +}; + +void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message) +{ + actionAfter(seconds, ErrorAfter(message)); +} + +class EventAfter +{ +public: + EventAfter(LLEventPump& pump, const LLSD& event): + mPump(pump), + mEvent(event) + {} + + void operator()() + { + mPump.post(mEvent); + } + +private: + LLEventPump& mPump; + LLSD mEvent; +}; + +void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event) +{ + actionAfter(seconds, EventAfter(*this, event)); +} + +bool LLEventTimeoutBase::post(const LLSD& event) +{ + cancel(); + return LLEventStream::post(event); +} + +void LLEventTimeoutBase::cancel() +{ + mMainloop.disconnect(); +} + +bool LLEventTimeoutBase::tick(const LLSD&) +{ + if (countdownElapsed()) + { + cancel(); + mAction(); + } + return false; // show event to other listeners +} + +LLEventTimeout::LLEventTimeout() {} + +LLEventTimeout::LLEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source) +{ +} + +void LLEventTimeout::setCountdown(F32 seconds) +{ + mTimer.setTimerExpirySec(seconds); +} + +bool LLEventTimeout::countdownElapsed() const +{ + return mTimer.hasExpired(); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h new file mode 100644 index 0000000000..fe1a631c6b --- /dev/null +++ b/indra/llcommon/lleventfilter.h @@ -0,0 +1,186 @@ +/** + * @file lleventfilter.h + * @author Nat Goodspeed + * @date 2009-03-05 + * @brief Define LLEventFilter: LLEventStream subclass with conditions + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTFILTER_H) +#define LL_LLEVENTFILTER_H + +#include "llevents.h" +#include "stdtypes.h" +#include "lltimer.h" +#include + +/** + * Generic base class + */ +class LLEventFilter: public LLEventStream +{ +public: + /// construct a standalone LLEventFilter + LLEventFilter(const std::string& name="filter", bool tweak=true): + LLEventStream(name, tweak) + {} + /// construct LLEventFilter and connect it to the specified LLEventPump + LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true); + + /// Post an event to all listeners + virtual bool post(const LLSD& event) = 0; +}; + +/** + * Pass through only events matching a specified pattern + */ +class LLEventMatching: public LLEventFilter +{ +public: + /// Pass an LLSD map with keys and values the incoming event must match + LLEventMatching(const LLSD& pattern); + /// instantiate and connect + LLEventMatching(LLEventPump& source, const LLSD& pattern); + + /// Only pass through events matching the pattern + virtual bool post(const LLSD& event); + +private: + LLSD mPattern; +}; + +/** + * Wait for an event to be posted. If no such event arrives within a specified + * time, take a specified action. See LLEventTimeout for production + * implementation. + * + * @NOTE This is an abstract base class so that, for testing, we can use an + * alternate "timer" that doesn't actually consume real time. + */ +class LLEventTimeoutBase: public LLEventFilter +{ +public: + /// construct standalone + LLEventTimeoutBase(); + /// construct and connect + LLEventTimeoutBase(LLEventPump& source); + + /// Callable, can be constructed with boost::bind() + typedef boost::function Action; + + /** + * Start countdown timer for the specified number of @a seconds. Forward + * all events. If any event arrives before timer expires, cancel timer. If + * no event arrives before timer expires, take specified @a action. + * + * This is a one-shot timer. Once it has either expired or been canceled, + * it is inert until another call to actionAfter(). + * + * Calling actionAfter() while an existing timer is running cheaply + * replaces that original timer. Thus, a valid use case is to detect + * idleness of some event source by calling actionAfter() on each new + * event. A rapid sequence of events will keep the timer from expiring; + * the first gap in events longer than the specified timer will fire the + * specified Action. + * + * Any post() call cancels the timer. To be satisfied with only a + * particular event, chain on an LLEventMatching that only passes such + * events: + * + * @code + * event ultimate + * source ---> LLEventMatching ---> LLEventTimeout ---> listener + * @endcode + * + * @NOTE + * The implementation relies on frequent events on the LLEventPump named + * "mainloop". + */ + void actionAfter(F32 seconds, const Action& action); + + /** + * Like actionAfter(), but where the desired Action is LL_ERRS + * termination. Pass the timeout time and the desired LL_ERRS @a message. + * + * This method is useful when, for instance, some async API guarantees an + * event, whether success or failure, within a stated time window. + * Instantiate an LLEventTimeout listening to that API and call + * errorAfter() on each async request with a timeout comfortably longer + * than the API's time guarantee (much longer than the anticipated + * "mainloop" granularity). + * + * Then if the async API breaks its promise, the program terminates with + * the specified LL_ERRS @a message. The client of the async API can + * therefore assume the guarantee is upheld. + * + * @NOTE + * errorAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to errorAfter(). + */ + void errorAfter(F32 seconds, const std::string& message); + + /** + * Like actionAfter(), but where the desired Action is a particular event + * for all listeners. Pass the timeout time and the desired @a event data. + * + * Suppose the timeout should only be satisfied by a particular event, but + * the ultimate listener must see all other incoming events as well, plus + * the timeout @a event if any: + * + * @code + * some LLEventMatching LLEventMatching + * event ---> for particular ---> LLEventTimeout ---> for timeout + * source event event \ + * \ \ ultimate + * `-----------------------------------------------------> listener + * @endcode + * + * Since a given listener can listen on more than one LLEventPump, we can + * set things up so it sees the set union of events from LLEventTimeout + * and the original event source. However, as LLEventTimeout passes + * through all incoming events, the "particular event" that satisfies the + * left LLEventMatching would reach the ultimate listener twice. So we add + * an LLEventMatching that only passes timeout events. + * + * @NOTE + * eventAfter() is implemented in terms of actionAfter(), so all remarks + * about calling actionAfter() also apply to eventAfter(). + */ + void eventAfter(F32 seconds, const LLSD& event); + + /// Pass event through, canceling the countdown timer + virtual bool post(const LLSD& event); + + /// Cancel timer without event + void cancel(); + +protected: + virtual void setCountdown(F32 seconds) = 0; + virtual bool countdownElapsed() const = 0; + +private: + bool tick(const LLSD&); + + LLBoundListener mMainloop; + Action mAction; +}; + +/// Production implementation of LLEventTimoutBase +class LLEventTimeout: public LLEventTimeoutBase +{ +public: + LLEventTimeout(); + LLEventTimeout(LLEventPump& source); + +protected: + virtual void setCountdown(F32 seconds); + virtual bool countdownElapsed() const; + +private: + LLTimer mTimer; +}; + +#endif /* ! defined(LL_LLEVENTFILTER_H) */ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index eb380ba7c8..7e3c6964dc 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -38,6 +38,7 @@ #pragma warning (pop) #endif // other Linden headers +#include "stringize.h" /***************************************************************************** * queue_names: specify LLEventPump names that should be instantiated as @@ -256,6 +257,12 @@ LLEventPump::~LLEventPump() // static data member const LLEventPump::NameList LLEventPump::empty; +std::string LLEventPump::inventName(const std::string& pfx) +{ + static long suffix = 0; + return STRINGIZE(pfx << suffix++); +} + LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, const NameList& after, const NameList& before) diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 2f6515a4cb..20061f09c6 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -28,13 +27,9 @@ #include #include // noncopyable #include -#include #include #include // reference_wrapper #include -#include -#include -#include #include #include #include "llsd.h" @@ -111,6 +106,9 @@ typedef LLStandardSignal::slot_type LLEventListener; /// Result of registering a listener, supports connected(), /// disconnect() and blocked() typedef boost::signals2::connection LLBoundListener; +/// Storing an LLBoundListener in LLTempBoundListener will disconnect the +/// referenced listener when the LLTempBoundListener instance is destroyed. +typedef boost::signals2::scoped_connection LLTempBoundListener; /** * A common idiom for event-based code is to accept either a callable -- @@ -254,14 +252,62 @@ namespace LLEventDetail const ConnectFunc& connect_func); } // namespace LLEventDetail +/***************************************************************************** +* LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1)). This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * LLEventPump::listen(). + * - The @c MySubclass object begins destruction. ~MySubclass() + * runs, destroying state specific to the subclass. (For instance, a + * Foo* data member is deleted but not zeroed.) + * - The listening method will not be disconnected until + * ~LLEventTrackable() runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the Foo* pointer that was + * deleted but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with boost::shared_ptr. + * Passing LLEventPump::listen() a boost::bind() expression + * involving a boost::weak_ptr is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + /***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses LLEventStream and LLEventQueue. + * + * @NOTE + * LLEventPump derives from LLEventTrackable so that when you "chain" + * LLEventPump instances together, they will automatically disconnect on + * destruction. Please see LLEventTrackable documentation for situations in + * which this may be perilous across threads. */ -class LLEventPump: boost::noncopyable +class LLEventPump: public LLEventTrackable { public: /** @@ -364,10 +410,22 @@ public: * themselves. listen() can throw any ListenError; see ListenError * subclasses. * - * If (as is typical) you pass a boost::bind() expression, - * listen() will inspect the components of that expression. If a bound - * object matches any of several cases, the connection will automatically - * be disconnected when that object is destroyed. + * The listener name must be unique among active listeners for this + * LLEventPump, else you get DupListenerName. If you don't care to invent + * a name yourself, use inventName(). (I was tempted to recognize e.g. "" + * and internally generate a distinct name for that case. But that would + * handle badly the scenario in which you want to add, remove, re-add, + * etc. the same listener: each new listen() call would necessarily + * perform a new dependency sort. Assuming you specify the same + * after/before lists each time, using inventName() when you first + * instantiate your listener, then passing the same name on each listen() + * call, allows us to optimize away the second and subsequent dependency + * sorts. + * + * If (as is typical) you pass a boost::bind() expression as @a + * listener, listen() will inspect the components of that expression. If a + * bound object matches any of several cases, the connection will + * automatically be disconnected when that object is destroyed. * * * You bind a boost::weak_ptr. * * Binding a boost::shared_ptr that way would ensure that the @@ -429,6 +487,9 @@ public: /// query virtual bool enabled() const { return mEnabled; } + /// Generate a distinct name for a listener -- see listen() + static std::string inventName(const std::string& pfx="listener"); + private: friend class LLEventPumps; /// flush queued events @@ -503,47 +564,8 @@ private: }; /***************************************************************************** -* LLEventTrackable and underpinnings +* Underpinnings *****************************************************************************/ -/** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1)). This will implicitly disconnect when the object - * referenced by @c instance is destroyed. - * - * @note - * LLEventTrackable doesn't address a couple of cases: - * * Object destroyed during call - * - You enter a slot call in thread A. - * - Thread B destroys the object, which of course disconnects it from any - * future slot calls. - * - Thread A's call uses 'this', which now refers to a defunct object. - * Undefined behavior results. - * * Call during destruction - * - @c MySubclass is derived from LLEventTrackable. - * - @c MySubclass registers one of its own methods using - * LLEventPump::listen(). - * - The @c MySubclass object begins destruction. ~MySubclass() - * runs, destroying state specific to the subclass. (For instance, a - * Foo* data member is deleted but not zeroed.) - * - The listening method will not be disconnected until - * ~LLEventTrackable() runs. - * - Before we get there, another thread posts data to the @c LLEventPump - * instance, calling the @c MySubclass method. - * - The method in question relies on valid @c MySubclass state. (For - * instance, it attempts to dereference the Foo* pointer that was - * deleted but not zeroed.) - * - Undefined behavior results. - * If you suspect you may encounter any such scenario, you're better off - * managing the lifespan of your object with boost::shared_ptr. - * Passing LLEventPump::listen() a boost::bind() expression - * involving a boost::weak_ptr is recognized specially, engaging - * thread-safe Boost.Signals2 machinery. - */ -typedef boost::signals2::trackable LLEventTrackable; - /** * We originally provided a suite of overloaded * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 0202a033c3..643720cebe 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -46,6 +46,11 @@ #endif #include "llsdserialize.h" +#include "stringize.h" + +#include +#include +#include // U32 LLSD ll_sd_from_U32(const U32 val) @@ -313,3 +318,261 @@ BOOL compare_llsd_with_template( return TRUE; } + +/***************************************************************************** +* Helpers for llsd_matches() +*****************************************************************************/ +// raw data used for LLSD::Type lookup +struct Data +{ + LLSD::Type type; + const char* name; +} typedata[] = +{ +#define def(type) { LLSD::type, #type + 4 } + def(TypeUndefined), + def(TypeBoolean), + def(TypeInteger), + def(TypeReal), + def(TypeString), + def(TypeUUID), + def(TypeDate), + def(TypeURI), + def(TypeBinary), + def(TypeMap), + def(TypeArray) +#undef def +}; + +// LLSD::Type lookup class into which we load the above static data +class TypeLookup +{ + typedef std::map MapType; + +public: + TypeLookup() + { + for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) + { + mMap[di->type] = di->name; + } + } + + std::string lookup(LLSD::Type type) const + { + MapType::const_iterator found = mMap.find(type); + if (found != mMap.end()) + { + return found->second; + } + return STRINGIZE(""); + } + +private: + MapType mMap; +}; + +// static instance of the lookup class +static const TypeLookup sTypes; + +// describe a mismatch; phrasing may want tweaking +const std::string op(" required instead of "); + +// llsd_matches() wants to identify specifically where in a complex prototype +// structure the mismatch occurred. This entails passing a prefix string, +// empty for the top-level call. If the prototype contains an array of maps, +// and the mismatch occurs in the second map in a key 'foo', we want to +// decorate the returned string with: "[1]['foo']: etc." On the other hand, we +// want to omit the entire prefix -- including colon -- if the mismatch is at +// top level. This helper accepts the (possibly empty) recursively-accumulated +// prefix string, returning either empty or the original string with colon +// appended. +static std::string colon(const std::string& pfx) +{ + if (pfx.empty()) + return pfx; + return pfx + ": "; +} + +// param type for match_types +typedef std::vector TypeVector; + +// The scalar cases in llsd_matches() use this helper. In most cases, we can +// accept not only the exact type specified in the prototype, but also other +// types convertible to the expected type. That implies looping over an array +// of such types. If the actual type doesn't match any of them, we want to +// provide a list of acceptable conversions as well as the exact type, e.g.: +// "Integer (or Boolean, Real, String) required instead of UUID". Both the +// implementation and the calling logic are simplified by separating out the +// expected type from the convertible types. +static std::string match_types(LLSD::Type expect, // prototype.type() + const TypeVector& accept, // types convertible to that type + LLSD::Type actual, // type we're checking + const std::string& pfx) // as for llsd_matches +{ + // Trivial case: if the actual type is exactly what we expect, we're good. + if (actual == expect) + return ""; + + // For the rest of the logic, build up a suitable error string as we go so + // we only have to make a single pass over the list of acceptable types. + // If we detect success along the way, we'll simply discard the partial + // error string. + std::ostringstream out; + out << colon(pfx) << sTypes.lookup(expect); + + // If there are any convertible types, append that list. + if (! accept.empty()) + { + out << " ("; + const char* sep = "or "; + for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); + ai != aend; ++ai, sep = ", ") + { + // Don't forget to return success if we match any of those types... + if (actual == *ai) + return ""; + out << sep << sTypes.lookup(*ai); + } + out << ')'; + } + // If we got this far, it's because 'actual' was not one of the acceptable + // types, so we must return an error. 'out' already contains colon(pfx) + // and the formatted list of acceptable types, so just append the mismatch + // phrase and the actual type. + out << op << sTypes.lookup(actual); + return out.str(); +} + +// see docstring in .h file +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) +{ + // An undefined prototype means that any data is valid. + // An undefined slot in an array or map prototype means that any data + // may fill that slot. + if (prototype.isUndefined()) + return ""; + // A prototype array must match a data array with at least as many + // entries. Moreover, every prototype entry must match the + // corresponding data entry. + if (prototype.isArray()) + { + if (! data.isArray()) + { + return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); + } + if (data.size() < prototype.size()) + { + return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op + << "Array size " << data.size()); + } + for (LLSD::Integer i = 0; i < prototype.size(); ++i) + { + std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A prototype map must match a data map. Every key in the prototype + // must have a corresponding key in the data map; every value in the + // prototype must match the corresponding key's value in the data. + if (prototype.isMap()) + { + if (! data.isMap()) + { + return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); + } + // If there are a number of keys missing from the data, it would be + // frustrating to a coder to discover them one at a time, with a big + // build each time. Enumerate all missing keys. + std::ostringstream out; + out << colon(pfx); + const char* init = "Map missing keys: "; + const char* sep = init; + for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) + { + if (! data.has(mi->first)) + { + out << sep << mi->first; + sep = ", "; + } + } + // So... are we missing any keys? + if (sep != init) + { + return out.str(); + } + // Good, the data block contains all the keys required by the + // prototype. Now match the prototype entries. + for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) + { + std::string match(llsd_matches(mi2->second, data[mi2->first], + STRINGIZE("['" << mi2->first << "']"))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A String prototype can match String, Boolean, Integer, Real, UUID, + // Date and URI, because any of these can be converted to String. + if (prototype.isString()) + { + static LLSD::Type accept[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeUUID, + LLSD::TypeDate, + LLSD::TypeURI + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // Boolean, Integer, Real match each other or String. TBD: ensure that + // a String value is numeric. + if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) + { + static LLSD::Type all[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeString + }; + // Funny business: shuffle the set of acceptable types to include all + // but the prototype's type. Get the acceptable types in a set. + std::set rest(boost::begin(all), boost::end(all)); + // Remove the prototype's type because we pass that separately. + rest.erase(prototype.type()); + return match_types(prototype.type(), + TypeVector(rest.begin(), rest.end()), + data.type(), + pfx); + } + // UUID, Date and URI match themselves or String. + if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) + { + static LLSD::Type accept[] = + { + LLSD::TypeString + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // We don't yet know the conversion semantics associated with any new LLSD + // data type that might be added, so until we've been extended to handle + // them, assume it's strict: the new type matches only itself. (This is + // true of Binary, which is why we don't handle that case separately.) Too + // bad LLSD doesn't define isConvertible(Type to, Type from). + return match_types(prototype.type(), TypeVector(), data.type(), pfx); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 501600f1d9..0752f8aff1 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -104,6 +104,61 @@ BOOL compare_llsd_with_template( const LLSD& template_llsd, LLSD& resultant_llsd); +/** + * Recursively determine whether a given LLSD data block "matches" another + * LLSD prototype. The returned string is empty() on success, non-empty() on + * mismatch. + * + * This function tests structure (types) rather than data values. It is + * intended for when a consumer expects an LLSD block with a particular + * structure, and must succinctly detect whether the arriving block is + * well-formed. For instance, a test of the form: + * @code + * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) + * @endcode + * could instead be expressed by initializing a prototype LLSD map with the + * required keys and writing: + * @code + * if (! llsd_matches(prototype, data).empty()) + * @endcode + * + * A non-empty return value is an error-message fragment intended to indicate + * to (English-speaking) developers where in the prototype structure the + * mismatch occurred. + * + * * If a slot in the prototype isUndefined(), then anything is valid at that + * place in the real object. (Passing prototype == LLSD() matches anything + * at all.) + * * An array in the prototype must match a data array at least that large. + * (Additional entries in the data array are ignored.) Every isDefined() + * entry in the prototype array must match the corresponding entry in the + * data array. + * * A map in the prototype must match a map in the data. Every key in the + * prototype map must match a corresponding key in the data map. (Additional + * keys in the data map are ignored.) Every isDefined() value in the + * prototype map must match the corresponding key's value in the data map. + * * Scalar values in the prototype are tested for @em type rather than value. + * For instance, a String in the prototype matches any String at all. In + * effect, storing an Integer at a particular place in the prototype asserts + * that the caller intends to apply asInteger() to the corresponding slot in + * the data. + * * A String in the prototype matches String, Boolean, Integer, Real, UUID, + * Date and URI, because asString() applied to any of these produces a + * meaningful result. + * * Similarly, a Boolean, Integer or Real in the prototype can match any of + * Boolean, Integer or Real in the data -- or even String. + * * UUID matches UUID or String. + * * Date matches Date or String. + * * URI matches URI or String. + * * Binary in the prototype matches only Binary in the data. + * + * @TODO: when a Boolean, Integer or Real in the prototype matches a String in + * the data, we should examine the String @em value to ensure it can be + * meaningfully converted to the requested type. The same goes for UUID, Date + * and URI. + */ +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); + // Simple function to copy data out of input & output iterators if // there is no need for casting. template LLSD llsd_copy_array(Input iter, Input end) diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h new file mode 100644 index 0000000000..fa12f944ef --- /dev/null +++ b/indra/llcommon/tests/listener.h @@ -0,0 +1,139 @@ +/** + * @file listener.h + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Useful for tests of the LLEventPump family of classes + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LISTENER_H) +#define LL_LISTENER_H + +#include "llsd.h" +#include + +/***************************************************************************** +* test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +/// Bear in mind that this is strictly for testing +class Listener +{ +public: + /// Every Listener is instantiated with a name + Listener(const std::string& name): + mName(name) + { +// std::cout << *this << ": ctor\n"; + } +/*==========================================================================*| + // These methods are only useful when trying to track Listener instance + // lifespan + Listener(const Listener& that): + mName(that.mName), + mLastEvent(that.mLastEvent) + { + std::cout << *this << ": copy\n"; + } + virtual ~Listener() + { + std::cout << *this << ": dtor\n"; + } +|*==========================================================================*/ + /// You can request the name + std::string getName() const { return mName; } + /// This is a typical listener method that returns 'false' when done, + /// allowing subsequent listeners on the LLEventPump to process the + /// incoming event. + bool call(const LLSD& event) + { +// std::cout << *this << "::call(" << event << ")\n"; + mLastEvent = event; + return false; + } + /// This is an alternate listener that returns 'true' when done, which + /// stops processing of the incoming event. + bool callstop(const LLSD& event) + { +// std::cout << *this << "::callstop(" << event << ")\n"; + mLastEvent = event; + return true; + } + /// ListenMethod can represent either call() or callstop(). + typedef bool (Listener::*ListenMethod)(const LLSD&); + /** + * This helper method is only because our test code makes so many + * repetitive listen() calls to ListenerMethods. In real code, you should + * call LLEventPump::listen() directly so it can examine the specific + * object you pass to boost::bind(). + */ + LLBoundListener listenTo(LLEventPump& pump, + ListenMethod method=&Listener::call, + const LLEventPump::NameList& after=LLEventPump::empty, + const LLEventPump::NameList& before=LLEventPump::empty) + { + return pump.listen(getName(), boost::bind(method, this, _1), after, before); + } + /// Both call() and callstop() set mLastEvent. Retrieve it. + LLSD getLastEvent() const + { +// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; + return mLastEvent; + } + /// Reset mLastEvent to a known state. + void reset(const LLSD& to = LLSD()) + { +// std::cout << *this << "::reset(" << to << ")\n"; + mLastEvent = to; + } + +private: + std::string mName; + LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ + out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; + return out; +} + +/** + * This class tests the relative order in which various listeners on a given + * LLEventPump are called. Each listen() call binds a particular string, which + * we collect for later examination. The actual event is ignored. + */ +struct Collect +{ + bool add(const std::string& bound, const LLSD& event) + { + result.push_back(bound); + return false; + } + void clear() { result.clear(); } + typedef std::vector StringList; + StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ + out << '('; + Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); + if (begin != end) + { + out << '"' << *begin << '"'; + while (++begin != end) + { + out << ", \"" << *begin << '"'; + } + } + out << ')'; + return out; +} + +#endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp new file mode 100644 index 0000000000..28b909298e --- /dev/null +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -0,0 +1,276 @@ +/** + * @file lleventfilter_test.cpp + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Test for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" +#include "listener.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +* Test classes +*****************************************************************************/ +// Strictly speaking, we're testing LLEventTimeoutBase rather than the +// production LLEventTimeout (using LLTimer) because we don't want every test +// run to pause for some number of seconds until we reach a real timeout. But +// as we've carefully put all functionality except actual LLTimer calls into +// LLEventTimeoutBase, that should suffice. We're not not not trying to test +// LLTimer here. +class TestEventTimeout: public LLEventTimeoutBase +{ +public: + TestEventTimeout(): + mElapsed(true) + {} + TestEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source), + mElapsed(true) + {} + + // test hook + void forceTimeout(bool timeout=true) { mElapsed = timeout; } + +protected: + virtual void setCountdown(F32 seconds) { mElapsed = false; } + virtual bool countdownElapsed() const { return mElapsed; } + +private: + bool mElapsed; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct filter_data + { + // The resemblance between this test data and that in llevents_tut.cpp + // is not coincidental. + filter_data(): + pumps(LLEventPumps::instance()), + mainloop(pumps.obtain("mainloop")), + listener0("first"), + listener1("second") + {} + LLEventPumps& pumps; + LLEventPump& mainloop; + Listener listener0; + Listener listener1; + + void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) + { + ensure_equals(STRINGIZE(listener << ' ' << desc), + listener.getLastEvent(), got); + } + }; + typedef test_group filter_group; + typedef filter_group::object filter_object; + filter_group filtergrp("lleventfilter"); + + template<> template<> + void filter_object::test<1>() + { + set_test_name("LLEventMatching"); + LLEventPump& driver(pumps.obtain("driver")); + listener0.reset(0); + // Listener isn't derived from LLEventTrackable specifically to test + // various connection-management mechanisms. But that means we have a + // couple of transient Listener objects, one of which is listening to + // a persistent LLEventPump. Capture those connections in local + // LLTempBoundListener instances so they'll disconnect + // on destruction. + LLTempBoundListener temp1( + listener0.listenTo(driver)); + // Construct a pattern LLSD: desired Event must have a key "foo" + // containing string "bar" + LLEventMatching filter(driver, LLSD().insert("foo", "bar")); + listener1.reset(0); + LLTempBoundListener temp2( + listener1.listenTo(filter)); + driver.post(1); + check_listener("direct", listener0, LLSD(1)); + check_listener("filtered", listener1, LLSD(0)); + // Okay, construct an LLSD map matching the pattern + LLSD data; + data["foo"] = "bar"; + data["random"] = 17; + driver.post(data); + check_listener("direct", listener0, data); + check_listener("filtered", listener1, data); + } + + template<> template<> + void filter_object::test<2>() + { + set_test_name("LLEventTimeout::actionAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + // Use listener1.call() as the Action for actionAfter(), since it + // already provides a way to sense the call + listener1.reset(0); + // driver --> filter --> listener0 + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener1, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener1, LLSD(0)); + // Verify chained actionAfter() calls, that is, that a second + // actionAfter() resets the timer established by the first + // actionAfter(). + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Since our TestEventTimeout class isn't actually manipulating time + // (quantities of seconds), only a bool "elapsed" flag, sense that by + // forcing the flag between actionAfter() calls. + filter.forceTimeout(); + // Pumping mainloop here would result in a timeout (as we'll verify + // below). This state simulates a ticking timer that has not yet timed + // out. But now, before a mainloop event lets 'filter' recognize + // timeout on the previous actionAfter() call, pretend we're pushing + // that timeout farther into the future. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Look ma, no timeout! + mainloop.post(17); + check_listener("no timeout 3", listener1, LLSD(0)); + // Now let the updated actionAfter() timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener1, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener1.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 4", listener1, LLSD(0)); + // Reset the timer and then cancel() it. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // neither expired nor satisified + mainloop.post(17); + check_listener("no timeout 5", listener1, LLSD(0)); + // cancel + filter.cancel(); + // timeout! + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 6", listener1, LLSD(0)); + } + + template<> template<> + void filter_object::test<3>() + { + set_test_name("LLEventTimeout::eventAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.eventAfter(20, LLSD("timeout")); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.eventAfter(20, LLSD("timeout")); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener0, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } + + template<> template<> + void filter_object::test<4>() + { + set_test_name("LLEventTimeout::errorAfter()"); + WrapLL_ERRS capture; + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.errorAfter(20, "timeout"); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.errorAfter(20, "timeout"); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + std::string threw; + try + { + mainloop.post(17); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("errorAfter() timeout exception", threw, "timeout"); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } +} // namespace tut + +/***************************************************************************** +* Link dependencies +*****************************************************************************/ +#include "llsdutil.cpp" diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h new file mode 100644 index 0000000000..1001ebc466 --- /dev/null +++ b/indra/llcommon/tests/wrapllerrs.h @@ -0,0 +1,56 @@ +/** + * @file wrapllerrs.h + * @author Nat Goodspeed + * @date 2009-03-11 + * @brief Define a class useful for unit tests that engage llerrs (LL_ERRS) functionality + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRAPLLERRS_H) +#define LL_WRAPLLERRS_H + +#include "llerrorcontrol.h" + +struct WrapLL_ERRS +{ + WrapLL_ERRS(): + // Resetting Settings discards the default Recorder that writes to + // stderr. Otherwise, expected llerrs (LL_ERRS) messages clutter the + // console output of successful tests, potentially confusing things. + mPriorErrorSettings(LLError::saveAndResetSettings()), + // Save shutdown function called by LL_ERRS + mPriorFatal(LLError::getFatalFunction()) + { + // Make LL_ERRS call our own operator() method + LLError::setFatalFunction(boost::bind(&WrapLL_ERRS::operator(), this, _1)); + } + + ~WrapLL_ERRS() + { + LLError::setFatalFunction(mPriorFatal); + LLError::restoreSettings(mPriorErrorSettings); + } + + struct FatalException: public std::runtime_error + { + FatalException(const std::string& what): std::runtime_error(what) {} + }; + + void operator()(const std::string& message) + { + // Save message for later in case consumer wants to sense the result directly + error = message; + // Also throw an appropriate exception since calling code is likely to + // assume that control won't continue beyond LL_ERRS. + throw FatalException(message); + } + + std::string error; + LLError::Settings* mPriorErrorSettings; + LLError::FatalFunction mPriorFatal; +}; + +#endif /* ! defined(LL_WRAPLLERRS_H) */ diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index c0f7a4d335..99bd98dfc1 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories( set(llmessage_SOURCE_FILES llares.cpp + llareslistener.cpp llassetstorage.cpp llblowfishcipher.cpp llbuffer.cpp @@ -104,6 +105,7 @@ set(llmessage_HEADER_FILES CMakeLists.txt llares.h + llareslistener.h llassetstorage.h llblowfishcipher.h llbuffer.h @@ -222,4 +224,5 @@ IF (NOT LINUX AND VIEWER) ADD_BUILD_TEST(lltemplatemessagedispatcher llmessage) # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") + ADD_BUILD_TEST(llareslistener llmessage) ENDIF (NOT LINUX AND VIEWER) diff --git a/indra/llmessage/llares.cpp b/indra/llmessage/llares.cpp index fe37fe8142..acbf51d75c 100644 --- a/indra/llmessage/llares.cpp +++ b/indra/llmessage/llares.cpp @@ -33,6 +33,7 @@ */ #include "linden_common.h" +#include "llares.h" #include #include @@ -42,9 +43,10 @@ #include "apr_poll.h" #include "llapr.h" -#include "llares.h" +#include "llareslistener.h" #if defined(LL_WINDOWS) +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally # define ns_c_in 1 # define NS_HFIXEDSZ 12 /* #/bytes of fixed data in header */ # define NS_QFIXEDSZ 4 /* #/bytes of fixed data in query */ @@ -102,7 +104,9 @@ void LLAres::QueryResponder::queryError(int code) } LLAres::LLAres() : -chan_(NULL), mInitSuccess(false) + chan_(NULL), + mInitSuccess(false), + mListener(new LLAresListener("LLAres", this)) { if (ares_init(&chan_) != ARES_SUCCESS) { diff --git a/indra/llmessage/llares.h b/indra/llmessage/llares.h index c709a08499..78febcd560 100644 --- a/indra/llmessage/llares.h +++ b/indra/llmessage/llares.h @@ -36,7 +36,13 @@ #define LL_LLARES_H #ifdef LL_WINDOWS +// ares.h is broken on windows in that it depends on types defined in ws2tcpip.h +// we need to include them first to work around it, but the headers issue warnings +# pragma warning(push) +# pragma warning(disable:4996) +# include # include +# pragma warning(pop) #endif #ifdef LL_STANDALONE @@ -49,7 +55,10 @@ #include "llrefcount.h" #include "lluri.h" +#include + class LLQueryResponder; +class LLAresListener; /** * @brief Supported DNS RR types. @@ -444,6 +453,9 @@ public: protected: ares_channel chan_; bool mInitSuccess; + // boost::scoped_ptr would actually fit the requirement better, but it + // can't handle incomplete types as boost::shared_ptr can. + boost::shared_ptr mListener; }; /** diff --git a/indra/llmessage/llareslistener.cpp b/indra/llmessage/llareslistener.cpp new file mode 100644 index 0000000000..8e1176cdd9 --- /dev/null +++ b/indra/llmessage/llareslistener.cpp @@ -0,0 +1,108 @@ +/** + * @file llareslistener.cpp + * @author Nat Goodspeed + * @date 2009-03-18 + * @brief Implementation for llareslistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llareslistener.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llares.h" +#include "llerror.h" +#include "llevents.h" + +LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares): + mAres(llares), + mBoundListener(LLEventPumps::instance(). + obtain(pumpname). + listen("LLAresListener", boost::bind(&LLAresListener::process, this, _1))) +{ + mDispatch["rewriteURI"] = boost::bind(&LLAresListener::rewriteURI, this, _1); +} + +bool LLAresListener::process(const LLSD& command) +{ + const std::string op(command["op"]); + // Look up the requested operation. + DispatchMap::const_iterator found = mDispatch.find(op); + if (found == mDispatch.end()) + { + // There's no feedback other than our own reply. If somebody asks + // for an operation that's not supported (perhaps because of a + // typo?), unless we holler loudly, the request will be silently + // ignored. Throwing a tantrum on such errors will hopefully make + // this product more robust. + LL_ERRS("LLAresListener") << "Unsupported request " << op << LL_ENDL; + return false; + } + // Having found the operation, call it. + found->second(command); + // Conventional LLEventPump listener return + return false; +} + +/// This UriRewriteResponder subclass packages returned URIs as an LLSD +/// array to send back to the requester. +class UriRewriteResponder: public LLAres::UriRewriteResponder +{ +public: + /// Specify the event pump name on which to send the reply + UriRewriteResponder(const std::string& pumpname): + mPumpName(pumpname) + {} + + /// Called by base class with results. This is called in both the + /// success and error cases. On error, the calling logic passes the + /// original URI. + virtual void rewriteResult(const std::vector& uris) + { + LLSD result; + for (std::vector::const_iterator ui(uris.begin()), uend(uris.end()); + ui != uend; ++ui) + { + result.append(*ui); + } + LLEventPumps::instance().obtain(mPumpName).post(result); + } + +private: + const std::string mPumpName; +}; + +void LLAresListener::rewriteURI(const LLSD& data) +{ + const std::string uri(data["uri"]); + const std::string reply(data["reply"]); + // Validate that the request is well-formed + if (uri.empty() || reply.empty()) + { + LL_ERRS("LLAresListener") << "rewriteURI request missing"; + std::string separator; + if (uri.empty()) + { + LL_CONT << " 'uri'"; + separator = " and"; + } + if (reply.empty()) + { + LL_CONT << separator << " 'reply'"; + } + LL_CONT << LL_ENDL; + } + // Looks as though we have what we need; issue the request + mAres->rewriteURI(uri, new UriRewriteResponder(reply)); +} diff --git a/indra/llmessage/llareslistener.h b/indra/llmessage/llareslistener.h new file mode 100644 index 0000000000..8835440c5d --- /dev/null +++ b/indra/llmessage/llareslistener.h @@ -0,0 +1,47 @@ +/** + * @file llareslistener.h + * @author Nat Goodspeed + * @date 2009-03-18 + * @brief LLEventPump API for LLAres. This header doesn't actually define the + * API; the API is defined by the pump name on which this class + * listens, and by the expected content of LLSD it receives. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLARESLISTENER_H) +#define LL_LLARESLISTENER_H + +#include +#include +#include +#include "llevents.h" + +class LLAres; +class LLSD; + +/// Listen on an LLEventPump with specified name for LLAres request events. +class LLAresListener +{ +public: + /// Specify the pump name on which to listen, and bind the LLAres instance + /// to use (e.g. gAres) + LLAresListener(const std::string& pumpname, LLAres* llares); + + /// Handle request events on the event pump specified at construction time + bool process(const LLSD& command); + +private: + /// command["op"] == "rewriteURI" + void rewriteURI(const LLSD& data); + + typedef boost::function Callable; + typedef std::map DispatchMap; + DispatchMap mDispatch; + LLTempBoundListener mBoundListener; + LLAres* mAres; +}; + +#endif /* ! defined(LL_LLARESLISTENER_H) */ diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp new file mode 100644 index 0000000000..b8306d0fd9 --- /dev/null +++ b/indra/llmessage/tests/llareslistener_test.cpp @@ -0,0 +1,194 @@ +/** + * @file llareslistener_test.cpp + * @author Mark Palange + * @date 2009-02-26 + * @brief Tests of llareslistener.h. + * + * $LicenseInfo:firstyear=2009&license=internal$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "../llareslistener.h" +// STL headers +#include +// std headers +// external library headers +#include + +// other Linden headers +#include "llsd.h" +#include "llares.h" +#include "../test/lltut.h" +#include "llevents.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +* Dummy stuff +*****************************************************************************/ +LLAres::LLAres(): + // Simulate this much of the real LLAres constructor: we need an + // LLAresListener instance. + mListener(new LLAresListener("LLAres", this)) +{} +LLAres::~LLAres() {} +void LLAres::rewriteURI(const std::string &uri, + LLAres::UriRewriteResponder *resp) +{ + // This is the only LLAres method I chose to implement. + // The effect is that LLAres returns immediately with + // a result that is equal to the input uri. + std::vector result; + result.push_back(uri); + resp->rewriteResult(result); +} + +LLAres::QueryResponder::~QueryResponder() {} +void LLAres::QueryResponder::queryError(int) {} +void LLAres::QueryResponder::queryResult(char const*, size_t) {} +LLQueryResponder::LLQueryResponder() {} +void LLQueryResponder::queryResult(char const*, size_t) {} +void LLQueryResponder::querySuccess() {} +void LLAres::UriRewriteResponder::queryError(int) {} +void LLAres::UriRewriteResponder::querySuccess() {} +void LLAres::UriRewriteResponder::rewriteResult(const std::vector& uris) {} + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct data + { + LLAres dummyAres; + }; + typedef test_group llareslistener_group; + typedef llareslistener_group::object object; + llareslistener_group llareslistenergrp("llareslistener"); + + struct ResponseCallback + { + std::vector mURIs; + bool operator()(const LLSD& response) + { + mURIs.clear(); + for (LLSD::array_const_iterator ri(response.beginArray()), rend(response.endArray()); + ri != rend; ++ri) + { + mURIs.push_back(*ri); + } + return false; + } + }; + + template<> template<> + void object::test<1>() + { + set_test_name("test event"); + // Tests the success and failure cases, since they both use + // the same code paths in the LLAres responder. + ResponseCallback response; + std::string pumpname("trigger"); + // Since we're asking LLEventPumps to obtain() the pump by the desired + // name, it will persist beyond the current scope, so ensure we + // disconnect from it when 'response' goes away. + LLTempBoundListener temp( + LLEventPumps::instance().obtain(pumpname).listen("rewriteURIresponse", + boost::bind(&ResponseCallback::operator(), &response, _1))); + // Now build an LLSD request that will direct its response events to + // that pump. + const std::string testURI("login.bar.com"); + LLSD request; + request["op"] = "rewriteURI"; + request["uri"] = testURI; + request["reply"] = pumpname; + LLEventPumps::instance().obtain("LLAres").post(request); + ensure_equals(response.mURIs.size(), 1); + ensure_equals(response.mURIs.front(), testURI); + } + + template<> template<> + void object::test<2>() + { + set_test_name("bad op"); + WrapLL_ERRS capture; + LLSD request; + request["op"] = "foo"; + std::string threw; + try + { + LLEventPumps::instance().obtain("LLAres").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("LLAresListener bad op", threw, "Unsupported"); + } + + template<> template<> + void object::test<3>() + { + set_test_name("bad rewriteURI request"); + WrapLL_ERRS capture; + LLSD request; + request["op"] = "rewriteURI"; + std::string threw; + try + { + LLEventPumps::instance().obtain("LLAres").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("LLAresListener bad op", threw, "missing 'uri' and 'reply'"); + } + + template<> template<> + void object::test<4>() + { + set_test_name("bad rewriteURI request"); + WrapLL_ERRS capture; + LLSD request; + request["op"] = "rewriteURI"; + request["reply"] = "nonexistent"; + std::string threw; + try + { + LLEventPumps::instance().obtain("LLAres").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("LLAresListener bad op", threw, "missing 'uri'"); + } + + template<> template<> + void object::test<5>() + { + set_test_name("bad rewriteURI request"); + WrapLL_ERRS capture; + LLSD request; + request["op"] = "rewriteURI"; + request["uri"] = "foo.bar.com"; + std::string threw; + try + { + LLEventPumps::instance().obtain("LLAres").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("LLAresListener bad op", threw, "missing 'reply'"); + } +} diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py index e62f20912b..86d5761b1b 100644 --- a/indra/llmessage/tests/test_llsdmessage_peer.py +++ b/indra/llmessage/tests/test_llsdmessage_peer.py @@ -16,16 +16,12 @@ import os import sys from threading import Thread from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler + mydir = os.path.dirname(__file__) # expected to be .../indra/llmessage/tests/ sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) from indra.util.fastest_elementtree import parse as xml_parse from indra.base import llsd - -def debug(*args): - sys.stdout.writelines(args) - sys.stdout.flush() -# comment out the line below to enable debug output -debug = lambda *args: None +from testrunner import run, debug class TestHTTPRequestHandler(BaseHTTPRequestHandler): """This subclass of BaseHTTPRequestHandler is to receive and echo @@ -106,25 +102,5 @@ class TestHTTPServer(Thread): debug("Starting HTTP server...\n") httpd.serve_forever() -def main(*args): - # Start HTTP server thread. Note that this and all other comm server - # threads should be daemon threads: we'll let them run "forever," - # confident that the whole process will terminate when the main thread - # terminates, which will be when the test executable child process - # terminates. - httpThread = TestHTTPServer(name="httpd") - httpThread.setDaemon(True) - httpThread.start() - # choice of os.spawnv(): - # - [v vs. l] pass a list of args vs. individual arguments, - # - [no p] don't use the PATH because we specifically want to invoke the - # executable passed as our first arg, - # - [no e] child should inherit this process's environment. - debug("Running %s...\n" % (" ".join(args))) - sys.stdout.flush() - rc = os.spawnv(os.P_WAIT, args[0], args) - debug("%s returned %s\n" % (args[0], rc)) - return rc - if __name__ == "__main__": - sys.exit(main(*sys.argv[1:])) + sys.exit(run(server=TestHTTPServer(name="httpd"), *sys.argv[1:])) diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py new file mode 100644 index 0000000000..3b9c3a7a19 --- /dev/null +++ b/indra/llmessage/tests/testrunner.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +"""\ +@file testrunner.py +@author Nat Goodspeed +@date 2009-03-20 +@brief Utilities for writing wrapper scripts for ADD_COMM_BUILD_TEST unit tests + +$LicenseInfo:firstyear=2009&license=viewergpl$ +Copyright (c) 2009, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +import sys + +def debug(*args): + sys.stdout.writelines(args) + sys.stdout.flush() +# comment out the line below to enable debug output +debug = lambda *args: None + +def run(*args, **kwds): + """All positional arguments collectively form a command line, executed as + a synchronous child process. + In addition, pass server=new_thread_instance as an explicit keyword (to + differentiate it from an additional command-line argument). + new_thread_instance should be an instantiated but not yet started Thread + subclass instance, e.g.: + run("python", "-c", 'print "Hello, world!"', server=TestHTTPServer(name="httpd")) + """ + # If there's no server= keyword arg, don't start a server thread: simply + # run a child process. + try: + thread = kwds.pop("server") + except KeyError: + pass + else: + # Start server thread. Note that this and all other comm server + # threads should be daemon threads: we'll let them run "forever," + # confident that the whole process will terminate when the main thread + # terminates, which will be when the child process terminates. + thread.setDaemon(True) + thread.start() + # choice of os.spawnv(): + # - [v vs. l] pass a list of args vs. individual arguments, + # - [no p] don't use the PATH because we specifically want to invoke the + # executable passed as our first arg, + # - [no e] child should inherit this process's environment. + debug("Running %s...\n" % (" ".join(args))) + sys.stdout.flush() + rc = os.spawnv(os.P_WAIT, args[0], args) + debug("%s returned %s\n" % (args[0], rc)) + return rc diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index e1f545adb5..5d79dfbc3e 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -37,6 +37,7 @@ include(UI) include(UnixInstall) include(LLKDU) include(ViewerMiscLibs) +include(LLLogin) if (WINDOWS) include(CopyWinLibs) @@ -61,6 +62,7 @@ include_directories( ${LLXML_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS}/lscript_compile + ${LLLOGIN_INCLUDE_DIRS} ) set(viewer_SOURCE_FILES @@ -231,6 +233,7 @@ set(viewer_SOURCE_FILES lllocationinputctrl.cpp lllogchat.cpp llloginhandler.cpp + lllogininstance.cpp llmanip.cpp llmaniprotate.cpp llmanipscale.cpp @@ -315,7 +318,6 @@ set(viewer_SOURCE_FILES llslurl.cpp llspatialpartition.cpp llsprite.cpp - llsrv.cpp llstartup.cpp llstatusbar.cpp llstylemap.cpp @@ -353,7 +355,6 @@ set(viewer_SOURCE_FILES llurlhistory.cpp llurlsimstring.cpp llurlwhitelist.cpp - lluserauth.cpp llvectorperfoptions.cpp llviewchildren.cpp llviewerassetstorage.cpp @@ -432,6 +433,7 @@ set(viewer_SOURCE_FILES llworld.cpp llworldmap.cpp llworldmapview.cpp + llxmlrpclistener.cpp llxmlrpctransaction.cpp noise.cpp pipeline.cpp @@ -627,6 +629,7 @@ set(viewer_HEADER_FILES lllocationinputctrl.h lllogchat.h llloginhandler.h + lllogininstance.h llmanip.h llmaniprotate.h llmanipscale.h @@ -712,7 +715,6 @@ set(viewer_HEADER_FILES llslurl.h llspatialpartition.h llsprite.h - llsrv.h llstartup.h llstatusbar.h llstylemap.h @@ -752,7 +754,6 @@ set(viewer_HEADER_FILES llurlhistory.h llurlsimstring.h llurlwhitelist.h - lluserauth.h llvectorperfoptions.h llviewchildren.h llviewerassetstorage.h @@ -832,6 +833,7 @@ set(viewer_HEADER_FILES llworld.h llworldmap.h llworldmapview.h + llxmlrpclistener.h llxmlrpctransaction.h macmain.h noise.h @@ -1266,6 +1268,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${WINDOWS_LIBRARIES} ${XMLRPCEPI_LIBRARIES} ${ELFIO_LIBRARIES} + ${LLLOGIN_LIBRARIES} ) build_version(viewer) @@ -1390,3 +1393,5 @@ endif (INSTALL) ADD_VIEWER_BUILD_TEST(llagentaccess viewer) ADD_VIEWER_COMM_BUILD_TEST(llcapabilitylistener viewer ${CMAKE_CURRENT_SOURCE_DIR}/../llmessage/tests/test_llsdmessage_peer.py) +ADD_VIEWER_COMM_BUILD_TEST(llxmlrpclistener viewer + ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llxmlrpc_peer.py) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 073b6b85fc..455e987da0 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -142,7 +142,6 @@ #include "llfolderview.h" #include "lltoolbar.h" #include "llagentpilot.h" -#include "llsrv.h" #include "llvovolume.h" #include "llflexibleobject.h" #include "llvosurfacepatch.h" @@ -204,9 +203,6 @@ BOOL gAllowTapTapHoldRun = TRUE; BOOL gShowObjectUpdates = FALSE; BOOL gUseQuickTime = TRUE; -BOOL gAcceptTOS = FALSE; -BOOL gAcceptCriticalMessage = FALSE; - eLastExecEvent gLastExecEvent = LAST_EXEC_NORMAL; LLSD gDebugInfo; diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 536abfae58..a7f1594d0e 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -258,10 +258,6 @@ extern LLSD gDebugInfo; extern BOOL gAllowTapTapHoldRun; extern BOOL gShowObjectUpdates; -extern BOOL gAcceptTOS; -extern BOOL gAcceptCriticalMessage; - - typedef enum { LAST_EXEC_NORMAL = 0, diff --git a/indra/newview/llclassifiedinfo.cpp b/indra/newview/llclassifiedinfo.cpp index 5cf1579d0e..5fcafbeca6 100644 --- a/indra/newview/llclassifiedinfo.cpp +++ b/indra/newview/llclassifiedinfo.cpp @@ -38,35 +38,19 @@ LLClassifiedInfo::cat_map LLClassifiedInfo::sCategories; // static -void LLClassifiedInfo::loadCategories(LLUserAuth::options_t classified_options) +void LLClassifiedInfo::loadCategories(const LLSD& options) { - LLUserAuth::options_t::iterator resp_it; - for (resp_it = classified_options.begin(); - resp_it != classified_options.end(); - ++resp_it) + for(LLSD::array_const_iterator resp_it = options.beginArray(), + end = options.endArray(); resp_it != end; ++resp_it) { - const LLUserAuth::response_t& response = *resp_it; - - LLUserAuth::response_t::const_iterator option_it; - - S32 cat_id = 0; - option_it = response.find("category_id"); - if (option_it != response.end()) + LLSD name = (*resp_it)["category_name"]; + if(name.isDefined()) { - cat_id = atoi(option_it->second.c_str()); + LLSD id = (*resp_it)["category_id"]; + if(id.isDefined()) + { + LLClassifiedInfo::sCategories[id.asInteger()] = name.asString(); + } } - else - { - continue; - } - - // Add the category id/name pair - option_it = response.find("category_name"); - if (option_it != response.end()) - { - LLClassifiedInfo::sCategories[cat_id] = option_it->second; - } - } - } diff --git a/indra/newview/llclassifiedinfo.h b/indra/newview/llclassifiedinfo.h index cc5a6bf28f..37134c7e5b 100644 --- a/indra/newview/llclassifiedinfo.h +++ b/indra/newview/llclassifiedinfo.h @@ -37,7 +37,6 @@ #include "v3dmath.h" #include "lluuid.h" -#include "lluserauth.h" class LLMessageSystem; @@ -46,7 +45,7 @@ class LLClassifiedInfo public: LLClassifiedInfo() {} - static void loadCategories(LLUserAuth::options_t event_options); + static void loadCategories(const LLSD& options); typedef std::map cat_map; static cat_map sCategories; diff --git a/indra/newview/lleventinfo.cpp b/indra/newview/lleventinfo.cpp index d4175b6c84..9be45d18fb 100644 --- a/indra/newview/lleventinfo.cpp +++ b/indra/newview/lleventinfo.cpp @@ -87,35 +87,19 @@ void LLEventInfo::unpack(LLMessageSystem *msg) } // static -void LLEventInfo::loadCategories(LLUserAuth::options_t event_options) +void LLEventInfo::loadCategories(const LLSD& options) { - LLUserAuth::options_t::iterator resp_it; - for (resp_it = event_options.begin(); - resp_it != event_options.end(); - ++resp_it) + for(LLSD::array_const_iterator resp_it = options.beginArray(), + end = options.endArray(); resp_it != end; ++resp_it) { - const LLUserAuth::response_t& response = *resp_it; - - LLUserAuth::response_t::const_iterator option_it; - - S32 cat_id = 0; - option_it = response.find("category_id"); - if (option_it != response.end()) + LLSD name = (*resp_it)["category_name"]; + if(name.isDefined()) { - cat_id = atoi(option_it->second.c_str()); + LLSD id = (*resp_it)["category_id"]; + if(id.isDefined()) + { + LLEventInfo::sCategories[id.asInteger()] = name.asString(); + } } - else - { - continue; - } - - // Add the category id/name pair - option_it = response.find("category_name"); - if (option_it != response.end()) - { - LLEventInfo::sCategories[cat_id] = option_it->second; - } - } - } diff --git a/indra/newview/lleventinfo.h b/indra/newview/lleventinfo.h index 880517a9f4..493c659983 100644 --- a/indra/newview/lleventinfo.h +++ b/indra/newview/lleventinfo.h @@ -37,7 +37,6 @@ #include "v3dmath.h" #include "lluuid.h" -#include "lluserauth.h" class LLMessageSystem; @@ -48,7 +47,7 @@ public: void unpack(LLMessageSystem *msg); - static void loadCategories(LLUserAuth::options_t event_options); + static void loadCategories(const LLSD& options); public: std::string mName; diff --git a/indra/newview/lleventnotifier.cpp b/indra/newview/lleventnotifier.cpp index c0fe327815..e54d78de2e 100644 --- a/indra/newview/lleventnotifier.cpp +++ b/indra/newview/lleventnotifier.cpp @@ -95,18 +95,16 @@ void LLEventNotifier::update() } } -void LLEventNotifier::load(const LLUserAuth::options_t& event_options) +void LLEventNotifier::load(const LLSD& event_options) { - LLUserAuth::options_t::const_iterator resp_it; - for (resp_it = event_options.begin(); - resp_it != event_options.end(); - ++resp_it) + for(LLSD::array_const_iterator resp_it = event_options.beginArray(), + end = event_options.endArray(); resp_it != end; ++resp_it) { - const LLUserAuth::response_t& response = *resp_it; + LLSD response = *resp_it; LLEventNotification *new_enp = new LLEventNotification(); - if (!new_enp->load(response)) + if(!new_enp->load(response)) { delete new_enp; continue; @@ -207,49 +205,46 @@ bool LLEventNotification::handleResponse(const LLSD& notification, const LLSD& r return false; } -BOOL LLEventNotification::load(const LLUserAuth::response_t &response) +BOOL LLEventNotification::load(const LLSD& response) { - - LLUserAuth::response_t::const_iterator option_it; BOOL event_ok = TRUE; - option_it = response.find("event_id"); - if (option_it != response.end()) + LLSD option = response.get("event_id"); + if (option.isDefined()) { - mEventID = atoi(option_it->second.c_str()); + mEventID = option.asInteger(); } else { event_ok = FALSE; } - option_it = response.find("event_name"); - if (option_it != response.end()) + option = response.get("event_name"); + if (option.isDefined()) { - llinfos << "Event: " << option_it->second << llendl; - mEventName = option_it->second; + llinfos << "Event: " << option.asString() << llendl; + mEventName = option.asString(); } else { event_ok = FALSE; } - - option_it = response.find("event_date"); - if (option_it != response.end()) + option = response.get("event_date"); + if (option.isDefined()) { - llinfos << "EventDate: " << option_it->second << llendl; - mEventDateStr = option_it->second; + llinfos << "EventDate: " << option.asString() << llendl; + mEventDateStr = option.asString(); } else { event_ok = FALSE; } - option_it = response.find("event_date_ut"); - if (option_it != response.end()) + option = response.get("event_date_ut"); + if (option.isDefined()) { - llinfos << "EventDate: " << option_it->second << llendl; - mEventDate = strtoul(option_it->second.c_str(), NULL, 10); + llinfos << "EventDate: " << option.asString() << llendl; + mEventDate = strtoul(option.asString().c_str(), NULL, 10); } else { @@ -261,44 +256,44 @@ BOOL LLEventNotification::load(const LLUserAuth::response_t &response) S32 x_region = 0; S32 y_region = 0; - option_it = response.find("grid_x"); - if (option_it != response.end()) + option = response.get("grid_x"); + if (option.isDefined()) { - llinfos << "GridX: " << option_it->second << llendl; - grid_x= atoi(option_it->second.c_str()); + llinfos << "GridX: " << option.asInteger() << llendl; + grid_x= option.asInteger(); } else { event_ok = FALSE; } - option_it = response.find("grid_y"); - if (option_it != response.end()) + option = response.get("grid_y"); + if (option.isDefined()) { - llinfos << "GridY: " << option_it->second << llendl; - grid_y = atoi(option_it->second.c_str()); + llinfos << "GridY: " << option.asInteger() << llendl; + grid_y = option.asInteger(); } else { event_ok = FALSE; } - option_it = response.find("x_region"); - if (option_it != response.end()) + option = response.get("x_region"); + if (option.isDefined()) { - llinfos << "RegionX: " << option_it->second << llendl; - x_region = atoi(option_it->second.c_str()); + llinfos << "RegionX: " << option.asInteger() << llendl; + x_region = option.asInteger(); } else { event_ok = FALSE; } - option_it = response.find("y_region"); - if (option_it != response.end()) + option = response.get("y_region"); + if (option.isDefined()) { - llinfos << "RegionY: " << option_it->second << llendl; - y_region = atoi(option_it->second.c_str()); + llinfos << "RegionY: " << option.asInteger() << llendl; + y_region = option.asInteger(); } else { diff --git a/indra/newview/lleventnotifier.h b/indra/newview/lleventnotifier.h index feb734948c..6fdde87646 100644 --- a/indra/newview/lleventnotifier.h +++ b/indra/newview/lleventnotifier.h @@ -34,7 +34,6 @@ #define LL_LLEVENTNOTIFIER_H #include "llframetimer.h" -#include "lluserauth.h" #include "v3dmath.h" class LLEventInfo; @@ -49,7 +48,7 @@ public: void update(); // Notify the user of the event if it's coming up - void load(const LLUserAuth::options_t& event_options); // In the format that it comes in from LLUserAuth + void load(const LLSD& event_options); // In the format that it comes in from login void add(LLEventInfo &event_info); // Add a new notification for an event void remove(U32 event_id); @@ -69,7 +68,7 @@ public: LLEventNotification(); virtual ~LLEventNotification(); - BOOL load(const LLUserAuth::response_t &en); // In the format it comes in from LLUserAuth + BOOL load(const LLSD& en); // In the format it comes in from login BOOL load(const LLEventInfo &event_info); // From existing event_info on the viewer. //void setEventID(const U32 event_id); //void setEventName(std::string &event_name); diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index 764a6a3498..c79e96a5e5 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -36,8 +36,6 @@ // viewer includes #include "llagent.h" -#include "llappviewer.h" -#include "llstartup.h" #include "llviewerstats.h" #include "llviewertexteditor.h" #include "llviewerwindow.h" @@ -58,11 +56,13 @@ LLFloaterTOS* LLFloaterTOS::sInstance = NULL; // static -LLFloaterTOS* LLFloaterTOS::show(ETOSType type, const std::string & message) +LLFloaterTOS* LLFloaterTOS::show(ETOSType type, + const std::string & message, + const YesNoCallback& callback) { if( !LLFloaterTOS::sInstance ) { - LLFloaterTOS::sInstance = new LLFloaterTOS(type, message); + LLFloaterTOS::sInstance = new LLFloaterTOS(type, message, callback); } if (type == TOS_TOS) @@ -78,12 +78,15 @@ LLFloaterTOS* LLFloaterTOS::show(ETOSType type, const std::string & message) } -LLFloaterTOS::LLFloaterTOS(ETOSType type, const std::string & message) +LLFloaterTOS::LLFloaterTOS(ETOSType type, + const std::string & message, + const YesNoCallback& callback) : LLModalDialog( std::string(" "), 100, 100 ), mType(type), mMessage(message), mWebBrowserWindowId( 0 ), - mLoadCompleteCount( 0 ) + mLoadCompleteCount( 0 ), + mCallback(callback) { } @@ -235,25 +238,12 @@ void LLFloaterTOS::onContinue( void* userdata ) { LLFloaterTOS* self = (LLFloaterTOS*) userdata; llinfos << "User agrees with TOS." << llendl; - if (self->mType == TOS_TOS) - { - gAcceptTOS = TRUE; - } - else - { - gAcceptCriticalMessage = TRUE; - } - // Testing TOS dialog - #if ! LL_RELEASE_FOR_DOWNLOAD - if ( LLStartUp::getStartupState() == STATE_LOGIN_WAIT ) + if(self->mCallback) { - LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + self->mCallback(true); } - else - #endif - LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); // Go back and finish authentication self->closeFloater(); // destroys this object } @@ -262,8 +252,12 @@ void LLFloaterTOS::onCancel( void* userdata ) { LLFloaterTOS* self = (LLFloaterTOS*) userdata; llinfos << "User disagrees with TOS." << llendl; - LLNotifications::instance().add("MustAgreeToLogIn", LLSD(), LLSD(), login_alert_done); - LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + + if(self->mCallback) + { + self->mCallback(false); + } + self->mLoadCompleteCount = 0; // reset counter for next time we come to TOS self->closeFloater(); // destroys this object } diff --git a/indra/newview/llfloatertos.h b/indra/newview/llfloatertos.h index dbec3ff8b6..67d2f0ceec 100644 --- a/indra/newview/llfloatertos.h +++ b/indra/newview/llfloatertos.h @@ -36,6 +36,7 @@ #include "llmodaldialog.h" #include "llassetstorage.h" #include "llwebbrowserctrl.h" +#include class LLButton; class LLRadioGroup; @@ -57,8 +58,12 @@ public: TOS_CRITICAL_MESSAGE = 1 }; + typedef boost::function YesNoCallback; + // Asset_id is overwritten with LLUUID::null when agree is clicked. - static LLFloaterTOS* show(ETOSType type, const std::string & message); + static LLFloaterTOS* show(ETOSType type, + const std::string & message, + const YesNoCallback& callback); BOOL postBuild(); @@ -74,13 +79,16 @@ public: private: // Asset_id is overwritten with LLUUID::null when agree is clicked. - LLFloaterTOS(ETOSType type, const std::string & message); + LLFloaterTOS(ETOSType type, + const std::string & message, + const YesNoCallback& callback); private: ETOSType mType; std::string mMessage; int mWebBrowserWindowId; int mLoadCompleteCount; + YesNoCallback mCallback; static LLFloaterTOS* sInstance; }; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 1176bf8735..4e2bb3e2e9 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1876,63 +1876,56 @@ bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const } bool LLInventoryModel::loadSkeleton( - const LLInventoryModel::options_t& options, + const LLSD& options, const LLUUID& owner_id) { lldebugs << "importing inventory skeleton for " << owner_id << llendl; typedef std::set, InventoryIDPtrLess> cat_set_t; cat_set_t temp_cats; + bool rv = true; - update_map_t child_counts; + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD folder_id = (*it)["folder_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD version = (*it)["version"]; + if(name.isDefined() + && folder_id.isDefined() + && parent_id.isDefined() + && version.isDefined() + && folder_id.asUUID().notNull() // if an id is null, it locks the viewer. + ) + { + LLPointer cat = new LLViewerInventoryCategory(owner_id); + cat->rename(name.asString()); + cat->setUUID(folder_id.asUUID()); + cat->setParent(parent_id.asUUID()); - LLUUID id; - LLAssetType::EType preferred_type; - bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - LLPointer cat = new LLViewerInventoryCategory(owner_id); - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator skel; - skel = (*it).find("name"); - if(skel == no_response) goto clean_cat; - cat->rename(std::string((*skel).second)); - skel = (*it).find("folder_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second); - // if an id is null, it locks the viewer. - if(id.isNull()) goto clean_cat; - cat->setUUID(id); - skel = (*it).find("parent_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second); - cat->setParent(id); - skel = (*it).find("type_default"); - if(skel == no_response) - { - preferred_type = LLAssetType::AT_NONE; + LLAssetType::EType preferred_type = LLAssetType::AT_NONE; + LLSD type_default = (*it)["type_default"]; + if(type_default.isDefined()) + { + preferred_type = (LLAssetType::EType)type_default.asInteger(); + } + cat->setPreferredType(preferred_type); + cat->setVersion(version.asInteger()); + temp_cats.insert(cat); } else { - S32 t = atoi((*skel).second.c_str()); - preferred_type = (LLAssetType::EType)t; + llwarns << "Unable to import near " << name.asString() << llendl; + rv = false; } - cat->setPreferredType(preferred_type); - skel = (*it).find("version"); - if(skel == no_response) goto clean_cat; - cat->setVersion(atoi((*skel).second.c_str())); - temp_cats.insert(cat); - continue; - clean_cat: - llwarns << "Unable to import near " << cat->getName() << llendl; - rv = false; - //delete cat; // automatic when cat is reasigned or destroyed } S32 cached_category_count = 0; S32 cached_item_count = 0; if(!temp_cats.empty()) { + update_map_t child_counts; cat_array_t categories; item_array_t items; std::string owner_id_str; @@ -1961,6 +1954,7 @@ bool LLInventoryModel::loadSkeleton( llinfos << "Unable to gunzip " << gzip_filename << llendl; } } + if(loadFromFile(inventory_filename, categories, items)) { // We were able to find a cache of files. So, use what we @@ -2085,85 +2079,84 @@ bool LLInventoryModel::loadSkeleton( return rv; } -bool LLInventoryModel::loadMeat( - const LLInventoryModel::options_t& options, const LLUUID& owner_id) +bool LLInventoryModel::loadMeat(const LLSD& options, const LLUUID& owner_id) { llinfos << "importing inventory for " << owner_id << llendl; - LLPermissions default_perm; - default_perm.init(LLUUID::null, owner_id, LLUUID::null, LLUUID::null); - LLPointer item; - LLUUID id; - LLAssetType::EType type; - LLInventoryType::EType inv_type; bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - item = new LLViewerInventoryItem; - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator meat; - meat = (*it).find("name"); - if(meat == no_response) goto clean_item; - item->rename(std::string((*meat).second)); - meat = (*it).find("item_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - item->setUUID(id); - meat = (*it).find("parent_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - item->setParent(id); - meat = (*it).find("type"); - if(meat == no_response) goto clean_item; - type = (LLAssetType::EType)atoi((*meat).second.c_str()); - item->setType(type); - meat = (*it).find("inv_type"); - if(meat != no_response) - { - inv_type = (LLInventoryType::EType)atoi((*meat).second.c_str()); - item->setInventoryType(inv_type); - } - meat = (*it).find("data_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - if(LLAssetType::AT_CALLINGCARD == type) - { - LLPermissions perm; - perm.init(id, owner_id, LLUUID::null, LLUUID::null); - item->setPermissions(perm); - } - else + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD item_id = (*it)["item_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD asset_type = (*it)["type"]; + LLSD data_id = (*it)["data_id"]; + if(name.isDefined() + && item_id.isDefined() + && parent_id.isDefined() + && asset_type.isDefined() + && data_id.isDefined()) { - meat = (*it).find("perm_mask"); - if(meat != no_response) + LLPointer item = new LLViewerInventoryItem; + item->rename(name.asString()); + item->setUUID(item_id.asUUID()); + item->setParent(parent_id.asUUID()); + LLAssetType::EType type = (LLAssetType::EType)asset_type.asInteger(); + item->setType(type); + + LLSD llsd_inv_type = (*it)["inv_type"]; + if(llsd_inv_type.isDefined()) { - PermissionMask perm_mask = atoi((*meat).second.c_str()); - default_perm.initMasks( - perm_mask, perm_mask, perm_mask, perm_mask, perm_mask); + LLInventoryType::EType inv_type = (LLInventoryType::EType)llsd_inv_type.asInteger(); + item->setInventoryType(inv_type); + } + + if(LLAssetType::AT_CALLINGCARD == type) + { + LLPermissions perm; + perm.init(data_id.asUUID(), owner_id, LLUUID::null, LLUUID::null); + item->setPermissions(perm); } else { - default_perm.initMasks( - PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); + LLPermissions default_perm; + default_perm.init(LLUUID::null, owner_id, LLUUID::null, LLUUID::null); + LLSD llsd_perm_mask = (*it)["perm_mask"]; + if(llsd_perm_mask.isDefined()) + { + PermissionMask perm_mask = llsd_perm_mask.asInteger(); + default_perm.initMasks( + perm_mask, perm_mask, perm_mask, perm_mask, perm_mask); + } + else + { + default_perm.initMasks( + PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); + } + item->setPermissions(default_perm); + item->setAssetUUID(data_id.asUUID()); } - item->setPermissions(default_perm); - item->setAssetUUID(id); - } - meat = (*it).find("flags"); - if(meat != no_response) - { - item->setFlags(strtoul((*meat).second.c_str(), NULL, 0)); + + LLSD flags = (*it)["flags"]; + if(flags.isDefined()) + { + // Not sure how well LLSD.asInteger() maps to + // unsigned long - using strtoul() + item->setFlags(strtoul(flags.asString().c_str(), NULL, 0)); + } + + LLSD time = (*it)["time"]; + if(time.isDefined()) + { + item->setCreationDate(time.asInteger()); + } + addItem(item); } - meat = (*it).find("time"); - if(meat != no_response) + else { - item->setCreationDate(atoi((*meat).second.c_str())); + llwarns << "Unable to import near " << name.asString() << llendl; + rv = false; } - addItem(item); - continue; - clean_item: - llwarns << "Unable to import near " << item->getName() << llendl; - rv = false; - //delete item; // automatic when item is reassigned or destroyed } return rv; } diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index d73fef7207..fcb3cc737a 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -314,10 +314,8 @@ public: // methods to load up inventory skeleton & meat. These are used // during authentication. return true if everything parsed. - typedef std::map response_t; - typedef std::vector options_t; - bool loadSkeleton(const options_t& options, const LLUUID& owner_id); - bool loadMeat(const options_t& options, const LLUUID& owner_id); + bool loadSkeleton(const LLSD& options, const LLUUID& owner_id); + bool loadMeat(const LLSD& options, const LLUUID& owner_id); // This is a brute force method to rebuild the entire parent-child // relations. diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp new file mode 100644 index 0000000000..388bf38d61 --- /dev/null +++ b/indra/newview/lllogininstance.cpp @@ -0,0 +1,532 @@ +/** + * @file lllogininstance.cpp + * @brief Viewer's host for a login connection. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lllogininstance.h" + +// llcommon +#include "llevents.h" +#include "llmd5.h" +#include "stringize.h" + +// llmessage (!) +#include "llfiltersd2xmlrpc.h" // for xml_escape_string() + +// login +#include "lllogin.h" + +// newview +#include "llviewernetwork.h" +#include "llappviewer.h" // Wish I didn't have to, but... +#include "llviewercontrol.h" +#include "llurlsimstring.h" +#include "llfloatertos.h" +#include "llwindow.h" + +std::string construct_start_string(); + +LLLoginInstance::LLLoginInstance() : + mLoginModule(new LLLogin()), + mLoginState("offline"), + mUserInteraction(true), + mSkipOptionalUpdate(false), + mAttemptComplete(false), + mTransferRate(0.0f) +{ + mLoginModule->getEventPump().listen("lllogininstance", + boost::bind(&LLLoginInstance::handleLoginEvent, this, _1)); +} + +LLLoginInstance::~LLLoginInstance() +{ +} + + +void LLLoginInstance::connect(const LLSD& credentials) +{ + std::vector uris; + LLViewerLogin::getInstance()->getLoginURIs(uris); + connect(uris.front(), credentials); +} + +void LLLoginInstance::connect(const std::string& uri, const LLSD& credentials) +{ + constructAuthParams(credentials); + mLoginModule->connect(uri, mRequestData); +} + +void LLLoginInstance::reconnect() +{ + // Sort of like connect, only using the pre-existing + // request params. + std::vector uris; + LLViewerLogin::getInstance()->getLoginURIs(uris); + mLoginModule->connect(uris.front(), mRequestData); +} + +void LLLoginInstance::disconnect() +{ + mRequestData.clear(); + mLoginModule->disconnect(); +} + +LLSD LLLoginInstance::getResponse() +{ + return mResponseData; +} + +void LLLoginInstance::constructAuthParams(const LLSD& credentials) +{ + // Set up auth request options. +//#define LL_MINIMIAL_REQUESTED_OPTIONS + LLSD requested_options; + // *Note: this is where gUserAuth used to be created. + requested_options.append("inventory-root"); + requested_options.append("inventory-skeleton"); + //requested_options.append("inventory-meat"); + //requested_options.append("inventory-skel-targets"); +#if (!defined LL_MINIMIAL_REQUESTED_OPTIONS) + if(FALSE == gSavedSettings.getBOOL("NoInventoryLibrary")) + { + requested_options.append("inventory-lib-root"); + requested_options.append("inventory-lib-owner"); + requested_options.append("inventory-skel-lib"); + // requested_options.append("inventory-meat-lib"); + } + + requested_options.append("initial-outfit"); + requested_options.append("gestures"); + requested_options.append("event_categories"); + requested_options.append("event_notifications"); + requested_options.append("classified_categories"); + //requested_options.append("inventory-targets"); + requested_options.append("buddy-list"); + requested_options.append("ui-config"); +#endif + requested_options.append("tutorial_setting"); + requested_options.append("login-flags"); + requested_options.append("global-textures"); + if(gSavedSettings.getBOOL("ConnectAsGod")) + { + gSavedSettings.setBOOL("UseDebugMenus", TRUE); + requested_options.append("god-connect"); + } + + char hashed_mac_string[MD5HEX_STR_SIZE]; /* Flawfinder: ignore */ + LLMD5 hashed_mac; + hashed_mac.update( gMACAddress, MAC_ADDRESS_BYTES ); + hashed_mac.finalize(); + hashed_mac.hex_digest(hashed_mac_string); + + // prepend "$1$" to the password to indicate its the md5'd version. + std::string dpasswd("$1$"); + dpasswd.append(credentials["passwd"].asString()); + + // (re)initialize the request params with creds. + LLSD request_params(credentials); + request_params["passwd"] = dpasswd; + request_params["start"] = construct_start_string(); + request_params["skipoptional"] = mSkipOptionalUpdate; + request_params["agree_to_tos"] = false; // Always false here. Set true in + request_params["read_critical"] = false; // handleTOSResponse + request_params["last_exec_event"] = gLastExecEvent; + request_params["mac"] = hashed_mac_string; + request_params["version"] = gCurrentVersion; // Includes channel name + request_params["channel"] = gSavedSettings.getString("VersionChannelName"); + request_params["id0"] = LLAppViewer::instance()->getSerialNumber(); + + mRequestData["method"] = "login_to_simulator"; + mRequestData["params"] = request_params; + mRequestData["options"] = requested_options; +} + +bool LLLoginInstance::handleLoginEvent(const LLSD& event) +{ + std::cout << "LoginListener called!: \n"; + std::cout << event << "\n"; + + if(!(event.has("state") && event.has("progress"))) + { + llerrs << "Unknown message from LLLogin!" << llendl; + } + + mLoginState = event["state"].asString(); + mResponseData = event["data"]; + + if(event.has("transfer_rate")) + { + mTransferRate = event["transfer_rate"].asReal(); + } + + if(mLoginState == "offline") + { + handleLoginFailure(event); + } + else if(mLoginState == "online") + { + handleLoginSuccess(event); + } + + return false; +} + +bool LLLoginInstance::handleLoginFailure(const LLSD& event) +{ + // Login has failed. + // Figure out why and respond... + LLSD response = event["data"]; + std::string reason_response = response["reason"].asString(); + std::string message_response = response["message"].asString(); + if(mUserInteraction) + { + // For the cases of critical message or TOS agreement, + // start the TOS dialog. The dialog response will be handled + // by the LLLoginInstance::handleTOSResponse() callback. + // The callback intiates the login attempt next step, either + // to reconnect or to end the attempt in failure. + if(reason_response == "tos") + { + LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_TOS, + message_response, + boost::bind(&LLLoginInstance::handleTOSResponse, + this, _1, "agree_to_tos") + ); + tos_dialog->startModal(); + } + else if(reason_response == "critical") + { + LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_CRITICAL_MESSAGE, + message_response, + boost::bind(&LLLoginInstance::handleTOSResponse, + this, _1, "read_critical") + ); + tos_dialog->startModal(); + } + else if(reason_response == "update" || gSavedSettings.getBOOL("ForceMandatoryUpdate")) + { + gSavedSettings.setBOOL("ForceMandatoryUpdate", FALSE); + updateApp(true, message_response); + } + else if(reason_response == "optional") + { + updateApp(false, message_response); + } + else + { + attemptComplete(); + } + } + else // no user interaction + { + attemptComplete(); + } + + return false; +} + +bool LLLoginInstance::handleLoginSuccess(const LLSD& event) +{ + LLSD response = event["data"]; + std::string message_response = response["message"].asString(); + if(gSavedSettings.getBOOL("ForceMandatoryUpdate")) + { + // Testing update... + gSavedSettings.setBOOL("ForceMandatoryUpdate", FALSE); + // Don't confuse startup by leaving login "online". + mLoginModule->disconnect(); + updateApp(true, message_response); + } + else + { + attemptComplete(); + } + return false; +} + +void LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) +{ + if(accepted) + { + // Set the request data to true and retry login. + mRequestData[key] = true; + reconnect(); + } + else + { + attemptComplete(); + } +} + + +void LLLoginInstance::updateApp(bool mandatory, const std::string& auth_msg) +{ + // store off config state, as we might quit soon + gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), TRUE); + + std::ostringstream message; + + //*TODO:translate + std::string msg; + if (!auth_msg.empty()) + { + msg = "(" + auth_msg + ") \n"; + } + + LLSD args; + args["MESSAGE"] = msg; + + LLSD payload; + payload["mandatory"] = mandatory; + +/* + We're constructing one of the following 6 strings here: + "DownloadWindowsMandatory" + "DownloadWindowsReleaseForDownload" + "DownloadWindows" + "DownloadMacMandatory" + "DownloadMacReleaseForDownload" + "DownloadMac" + + I've called them out explicitly in this comment so that they can be grepped for. + + Also, we assume that if we're not Windows we're Mac. If we ever intend to support + Linux with autoupdate, this should be an explicit #elif LL_DARWIN, but + we'd rather deliver the wrong message than no message, so until Linux is supported + we'll leave it alone. + */ + std::string notification_name = "Download"; + +#if LL_WINDOWS + notification_name += "Windows"; +#else + notification_name += "Mac"; +#endif + + if (mandatory) + { + notification_name += "Mandatory"; + } + else + { +#if LL_RELEASE_FOR_DOWNLOAD + notification_name += "ReleaseForDownload"; +#endif + } + + LLNotifications::instance().add(notification_name, args, payload, + boost::bind(&LLLoginInstance::updateDialogCallback, this, _1, _2)); +} + +bool LLLoginInstance::updateDialogCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + std::string update_exe_path; + bool mandatory = notification["payload"]["mandatory"].asBoolean(); + +#if !LL_RELEASE_FOR_DOWNLOAD + if (option == 2) + { + // This condition attempts to skip the + // update if using a dev build. + // The relog probably won't work if the + // update is mandatory. :) + + // *REMOVE:Mani - Saving for reference... + //LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); + mSkipOptionalUpdate = true; + reconnect(); + return false; + } +#endif + + if (option == 1) + { + // ...user doesn't want to do it + if (mandatory) + { + // Mandatory update, user chose to not to update... + // The login attemp is complete, startup should + // quit when detecting this. + attemptComplete(); + + // *REMOVE:Mani - Saving for reference... + //LLAppViewer::instance()->forceQuit(); + // // Bump them back to the login screen. + // //reset_login(); + } + else + { + // Optional update, user chose to skip + mSkipOptionalUpdate = true; + reconnect(); + } + return false; + } + + LLSD query_map = LLSD::emptyMap(); + // *TODO place os string in a global constant +#if LL_WINDOWS + query_map["os"] = "win"; +#elif LL_DARWIN + query_map["os"] = "mac"; +#elif LL_LINUX + query_map["os"] = "lnx"; +#elif LL_SOLARIS + query_map["os"] = "sol"; +#endif + // *TODO change userserver to be grid on both viewer and sim, since + // userserver no longer exists. + query_map["userserver"] = LLViewerLogin::getInstance()->getGridLabel(); + query_map["channel"] = gSavedSettings.getString("VersionChannelName"); + // *TODO constantize this guy + LLURI update_url = LLURI::buildHTTP("secondlife.com", 80, "update.php", query_map); + + if(LLAppViewer::sUpdaterInfo) + { + delete LLAppViewer::sUpdaterInfo; + } + LLAppViewer::sUpdaterInfo = new LLAppViewer::LLUpdaterInfo() ; + +#if LL_WINDOWS + LLAppViewer::sUpdaterInfo->mUpdateExePath = gDirUtilp->getTempFilename(); + if (LLAppViewer::sUpdaterInfo->mUpdateExePath.empty()) + { + delete LLAppViewer::sUpdaterInfo ; + LLAppViewer::sUpdaterInfo = NULL ; + + // We're hosed, bail + LL_WARNS("AppInit") << "LLDir::getTempFilename() failed" << LL_ENDL; + + attemptComplete(); + // *REMOVE:Mani - Saving for reference... + // LLAppViewer::instance()->forceQuit(); + return false; + } + + LLAppViewer::sUpdaterInfo->mUpdateExePath += ".exe"; + + std::string updater_source = gDirUtilp->getAppRODataDir(); + updater_source += gDirUtilp->getDirDelimiter(); + updater_source += "updater.exe"; + + LL_DEBUGS("AppInit") << "Calling CopyFile source: " << updater_source + << " dest: " << LLAppViewer::sUpdaterInfo->mUpdateExePath + << LL_ENDL; + + + if (!CopyFileA(updater_source.c_str(), LLAppViewer::sUpdaterInfo->mUpdateExePath.c_str(), FALSE)) + { + delete LLAppViewer::sUpdaterInfo ; + LLAppViewer::sUpdaterInfo = NULL ; + + LL_WARNS("AppInit") << "Unable to copy the updater!" << LL_ENDL; + attemptComplete(); + // *REMOVE:Mani - Saving for reference... + // LLAppViewer::instance()->forceQuit(); + return false; + } + + // if a sim name was passed in via command line parameter (typically through a SLURL) + if ( LLURLSimString::sInstance.mSimString.length() ) + { + // record the location to start at next time + gSavedSettings.setString( "NextLoginLocation", LLURLSimString::sInstance.mSimString ); + }; + + LLAppViewer::sUpdaterInfo->mParams << "-url \"" << update_url.asString() << "\""; + + LL_DEBUGS("AppInit") << "Calling updater: " << LLAppViewer::sUpdaterInfo->mUpdateExePath << " " << LLAppViewer::sUpdaterInfo->mParams.str() << LL_ENDL; + + //Explicitly remove the marker file, otherwise we pass the lock onto the child process and things get weird. + LLAppViewer::instance()->removeMarkerFile(); // In case updater fails + + // *NOTE:Mani The updater is spawned as the last thing before the WinMain exit. + // see LLAppViewerWin32.cpp + +#elif LL_DARWIN + // if a sim name was passed in via command line parameter (typically through a SLURL) + if ( LLURLSimString::sInstance.mSimString.length() ) + { + // record the location to start at next time + gSavedSettings.setString( "NextLoginLocation", LLURLSimString::sInstance.mSimString ); + }; + + LLAppViewer::sUpdaterInfo->mUpdateExePath = "'"; + LLAppViewer::sUpdaterInfo->mUpdateExePath += gDirUtilp->getAppRODataDir(); + LLAppViewer::sUpdaterInfo->mUpdateExePath += "/mac-updater.app/Contents/MacOS/mac-updater' -url \""; + LLAppViewer::sUpdaterInfo->mUpdateExePath += update_url.asString(); + LLAppViewer::sUpdaterInfo->mUpdateExePath += "\" -name \""; + LLAppViewer::sUpdaterInfo->mUpdateExePath += LLAppViewer::instance()->getSecondLifeTitle(); + LLAppViewer::sUpdaterInfo->mUpdateExePath += "\" &"; + + LL_DEBUGS("AppInit") << "Calling updater: " << LLAppViewer::sUpdaterInfo->mUpdateExePath << LL_ENDL; + + // Run the auto-updater. + system(LLAppViewer::sUpdaterInfo->mUpdateExePath.c_str()); /* Flawfinder: ignore */ + +#elif LL_LINUX || LL_SOLARIS + OSMessageBox("Automatic updating is not yet implemented for Linux.\n" + "Please download the latest version from www.secondlife.com.", + LLStringUtil::null, OSMB_OK); +#endif + + // *REMOVE:Mani - Saving for reference... + // LLAppViewer::instance()->forceQuit(); + + return false; +} + +std::string construct_start_string() +{ + std::string start; + if (LLURLSimString::parse()) + { + // a startup URL was specified + std::string unescaped_start = + STRINGIZE( "uri:" + << LLURLSimString::sInstance.mSimName << "&" + << LLURLSimString::sInstance.mX << "&" + << LLURLSimString::sInstance.mY << "&" + << LLURLSimString::sInstance.mZ); + start = xml_escape_string(unescaped_start); + } + else if (gSavedSettings.getBOOL("LoginLastLocation")) + { + start = "last"; + } + else + { + start = "home"; + } + return start; +} diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h new file mode 100644 index 0000000000..da70fec40e --- /dev/null +++ b/indra/newview/lllogininstance.h @@ -0,0 +1,95 @@ +/** + * @file lllogininstance.h + * @brief A host for the viewer's login connection. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLLOGININSTANCE_H +#define LL_LLLOGININSTANCE_H + +#include +class LLLogin; + +// This class hosts the login module and is used to +// negotiate user authentication attempts. +class LLLoginInstance : public LLSingleton +{ +public: + LLLoginInstance(); + ~LLLoginInstance(); + + void connect(const LLSD& credential); // Connect to the current grid choice. + void connect(const std::string& uri, const LLSD& credential); // Connect to the given uri. + void reconnect(); // reconnect using the current credentials. + void disconnect(); + + // Set whether this class will drive user interaction. + // If not, login failures like 'need tos agreement' will + // end the login attempt. + void setUserInteraction(bool state) { mUserInteraction = state; } + bool getUserInteraction() { return mUserInteraction; } + + // Whether to tell login to skip optional update request. + // False by default. + void setSkipOptionalUpdate(bool state) { mSkipOptionalUpdate = state; } + + bool authFailure() { return mAttemptComplete && mLoginState == "offline"; } + bool authSuccess() { return mAttemptComplete && mLoginState == "online"; } + + const std::string& getLoginState() { return mLoginState; } + LLSD getResponse(const std::string& key) { return getResponse()[key]; } + LLSD getResponse(); + + // Only valid when authSuccess == true. + const F64 getLastTransferRateBPS() { return mTransferRate; } + +private: + void constructAuthParams(const LLSD& credentials); + void updateApp(bool mandatory, const std::string& message); + bool updateDialogCallback(const LLSD& notification, const LLSD& response); + + bool handleLoginEvent(const LLSD& event); + bool handleLoginFailure(const LLSD& event); + bool handleLoginSuccess(const LLSD& event); + + void handleTOSResponse(bool v, const std::string& key); + + void attemptComplete() { mAttemptComplete = true; } // In the future an event? + + boost::scoped_ptr mLoginModule; + std::string mLoginState; + LLSD mRequestData; + LLSD mResponseData; + bool mUserInteraction; + bool mSkipOptionalUpdate; + bool mAttemptComplete; + F64 mTransferRate; +}; + +#endif diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 671d3264bb..06c78a93da 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -434,7 +434,7 @@ BOOL LLPanelLogin::handleKeyHere(KEY key, MASK mask) if ( KEY_F2 == key ) { llinfos << "Spawning floater TOS window" << llendl; - LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_TOS,""); + LLFloaterTOS* tos_dialog = LLFloaterTOS::show(LLFloaterTOS::TOS_TOS,"", NULL); tos_dialog->startModal(); return TRUE; } diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h index 93701800e9..5e89030a01 100644 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -50,11 +50,7 @@ typedef enum { STATE_LOGIN_SHOW, // Show login screen STATE_LOGIN_WAIT, // Wait for user input at login screen STATE_LOGIN_CLEANUP, // Get rid of login screen and start login - STATE_UPDATE_CHECK, // Wait for user at a dialog box (updates, term-of-service, etc) STATE_LOGIN_AUTH_INIT, // Start login to SL servers - STATE_LOGIN_AUTHENTICATE, // Do authentication voodoo - STATE_LOGIN_NO_DATA_YET, // Waiting for authentication replies to start - STATE_LOGIN_DOWNLOADING, // Waiting for authentication replies to download STATE_LOGIN_PROCESS_RESPONSE, // Check authentication reply STATE_WORLD_INIT, // Start building the world STATE_MULTIMEDIA_INIT, // Init the rest of multimedia library @@ -75,8 +71,6 @@ typedef enum { // exported symbols extern bool gAgentMovementCompleted; extern LLPointer gStartImageGL; -extern std::string gInitialOutfit; -extern std::string gInitialOutfitGender; // "male" or "female" class LLStartUp { diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index f70e5ad242..5647b6889b 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -177,7 +177,6 @@ #include "lltrans.h" #include "lluictrlfactory.h" #include "lluploaddialog.h" -#include "lluserauth.h" #include "lluuid.h" #include "llviewercamera.h" #include "llviewergenericmessage.h" diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index 918b15ef09..801c46035a 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -35,6 +35,8 @@ #include "llviewernetwork.h" #include "llviewercontrol.h" +#include "llevents.h" +#include "lllogin.h" struct LLGridData { @@ -155,6 +157,10 @@ LLViewerLogin::LLViewerLogin() : { } + LLViewerLogin::~LLViewerLogin() + { + } + void LLViewerLogin::setGridChoice(EGridInfo grid) { if(grid < 0 || grid >= GRID_INFO_COUNT) diff --git a/indra/newview/llviewernetwork.h b/indra/newview/llviewernetwork.h index 4001ed05c1..edae6dc47b 100644 --- a/indra/newview/llviewernetwork.h +++ b/indra/newview/llviewernetwork.h @@ -34,7 +34,10 @@ #ifndef LL_LLVIEWERNETWORK_H #define LL_LLVIEWERNETWORK_H +#include + class LLHost; +class LLLogin; enum EGridInfo { @@ -74,6 +77,7 @@ class LLViewerLogin : public LLSingleton { public: LLViewerLogin(); + ~LLViewerLogin(); void setGridChoice(EGridInfo grid); void setGridChoice(const std::string& grid_name); diff --git a/indra/newview/llxmlrpclistener.cpp b/indra/newview/llxmlrpclistener.cpp new file mode 100644 index 0000000000..2821e6c59f --- /dev/null +++ b/indra/newview/llxmlrpclistener.cpp @@ -0,0 +1,494 @@ +/** + * @file llxmlrpclistener.cpp + * @author Nat Goodspeed + * @date 2009-03-18 + * @brief Implementation for llxmlrpclistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + + +// Precompiled header +#include "llviewerprecompiledheaders.h" +// associated header +#include "llxmlrpclistener.h" +// STL headers +#include +#include +// std headers +// external library headers +#include +#include // boost::begin(), boost::end() +// other Linden headers +#include "llerror.h" +#include "stringize.h" +#include "llxmlrpctransaction.h" + +#include + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +template +class StatusMapperBase +{ + typedef std::map MapType; + +public: + StatusMapperBase(const std::string& desc): + mDesc(desc) + {} + + std::string lookup(STATUS status) const + { + typename MapType::const_iterator found = mMap.find(status); + if (found != mMap.end()) + { + return found->second; + } + return STRINGIZE(""); + } + +protected: + std::string mDesc; + MapType mMap; +}; + +class StatusMapper: public StatusMapperBase +{ +public: + StatusMapper(): StatusMapperBase("Status") + { + mMap[LLXMLRPCTransaction::StatusNotStarted] = "NotStarted"; + mMap[LLXMLRPCTransaction::StatusStarted] = "Started"; + mMap[LLXMLRPCTransaction::StatusDownloading] = "Downloading"; + mMap[LLXMLRPCTransaction::StatusComplete] = "Complete"; + mMap[LLXMLRPCTransaction::StatusCURLError] = "CURLError"; + mMap[LLXMLRPCTransaction::StatusXMLRPCError] = "XMLRPCError"; + mMap[LLXMLRPCTransaction::StatusOtherError] = "OtherError"; + } +}; + +static const StatusMapper sStatusMapper; + +class CURLcodeMapper: public StatusMapperBase +{ +public: + CURLcodeMapper(): StatusMapperBase("CURLcode") + { + // from curl.h +// skip the "CURLE_" prefix for each of these strings +#define def(sym) (mMap[sym] = #sym + 6) + def(CURLE_OK); + def(CURLE_UNSUPPORTED_PROTOCOL); /* 1 */ + def(CURLE_FAILED_INIT); /* 2 */ + def(CURLE_URL_MALFORMAT); /* 3 */ + def(CURLE_URL_MALFORMAT_USER); /* 4 - NOT USED */ + def(CURLE_COULDNT_RESOLVE_PROXY); /* 5 */ + def(CURLE_COULDNT_RESOLVE_HOST); /* 6 */ + def(CURLE_COULDNT_CONNECT); /* 7 */ + def(CURLE_FTP_WEIRD_SERVER_REPLY); /* 8 */ + def(CURLE_FTP_ACCESS_DENIED); /* 9 a service was denied by the FTP server + due to lack of access - when login fails + this is not returned. */ + def(CURLE_FTP_USER_PASSWORD_INCORRECT); /* 10 - NOT USED */ + def(CURLE_FTP_WEIRD_PASS_REPLY); /* 11 */ + def(CURLE_FTP_WEIRD_USER_REPLY); /* 12 */ + def(CURLE_FTP_WEIRD_PASV_REPLY); /* 13 */ + def(CURLE_FTP_WEIRD_227_FORMAT); /* 14 */ + def(CURLE_FTP_CANT_GET_HOST); /* 15 */ + def(CURLE_FTP_CANT_RECONNECT); /* 16 */ + def(CURLE_FTP_COULDNT_SET_BINARY); /* 17 */ + def(CURLE_PARTIAL_FILE); /* 18 */ + def(CURLE_FTP_COULDNT_RETR_FILE); /* 19 */ + def(CURLE_FTP_WRITE_ERROR); /* 20 */ + def(CURLE_FTP_QUOTE_ERROR); /* 21 */ + def(CURLE_HTTP_RETURNED_ERROR); /* 22 */ + def(CURLE_WRITE_ERROR); /* 23 */ + def(CURLE_MALFORMAT_USER); /* 24 - NOT USED */ + def(CURLE_UPLOAD_FAILED); /* 25 - failed upload "command" */ + def(CURLE_READ_ERROR); /* 26 - could open/read from file */ + def(CURLE_OUT_OF_MEMORY); /* 27 */ + /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + def(CURLE_OPERATION_TIMEOUTED); /* 28 - the timeout time was reached */ + def(CURLE_FTP_COULDNT_SET_ASCII); /* 29 - TYPE A failed */ + def(CURLE_FTP_PORT_FAILED); /* 30 - FTP PORT operation failed */ + def(CURLE_FTP_COULDNT_USE_REST); /* 31 - the REST command failed */ + def(CURLE_FTP_COULDNT_GET_SIZE); /* 32 - the SIZE command failed */ + def(CURLE_HTTP_RANGE_ERROR); /* 33 - RANGE "command" didn't work */ + def(CURLE_HTTP_POST_ERROR); /* 34 */ + def(CURLE_SSL_CONNECT_ERROR); /* 35 - wrong when connecting with SSL */ + def(CURLE_BAD_DOWNLOAD_RESUME); /* 36 - couldn't resume download */ + def(CURLE_FILE_COULDNT_READ_FILE); /* 37 */ + def(CURLE_LDAP_CANNOT_BIND); /* 38 */ + def(CURLE_LDAP_SEARCH_FAILED); /* 39 */ + def(CURLE_LIBRARY_NOT_FOUND); /* 40 */ + def(CURLE_FUNCTION_NOT_FOUND); /* 41 */ + def(CURLE_ABORTED_BY_CALLBACK); /* 42 */ + def(CURLE_BAD_FUNCTION_ARGUMENT); /* 43 */ + def(CURLE_BAD_CALLING_ORDER); /* 44 - NOT USED */ + def(CURLE_INTERFACE_FAILED); /* 45 - CURLOPT_INTERFACE failed */ + def(CURLE_BAD_PASSWORD_ENTERED); /* 46 - NOT USED */ + def(CURLE_TOO_MANY_REDIRECTS ); /* 47 - catch endless re-direct loops */ + def(CURLE_UNKNOWN_TELNET_OPTION); /* 48 - User specified an unknown option */ + def(CURLE_TELNET_OPTION_SYNTAX ); /* 49 - Malformed telnet option */ + def(CURLE_OBSOLETE); /* 50 - NOT USED */ + def(CURLE_SSL_PEER_CERTIFICATE); /* 51 - peer's certificate wasn't ok */ + def(CURLE_GOT_NOTHING); /* 52 - when this is a specific error */ + def(CURLE_SSL_ENGINE_NOTFOUND); /* 53 - SSL crypto engine not found */ + def(CURLE_SSL_ENGINE_SETFAILED); /* 54 - can not set SSL crypto engine as + default */ + def(CURLE_SEND_ERROR); /* 55 - failed sending network data */ + def(CURLE_RECV_ERROR); /* 56 - failure in receiving network data */ + def(CURLE_SHARE_IN_USE); /* 57 - share is in use */ + def(CURLE_SSL_CERTPROBLEM); /* 58 - problem with the local certificate */ + def(CURLE_SSL_CIPHER); /* 59 - couldn't use specified cipher */ + def(CURLE_SSL_CACERT); /* 60 - problem with the CA cert (path?) */ + def(CURLE_BAD_CONTENT_ENCODING); /* 61 - Unrecognized transfer encoding */ + def(CURLE_LDAP_INVALID_URL); /* 62 - Invalid LDAP URL */ + def(CURLE_FILESIZE_EXCEEDED); /* 63 - Maximum file size exceeded */ + def(CURLE_FTP_SSL_FAILED); /* 64 - Requested FTP SSL level failed */ + def(CURLE_SEND_FAIL_REWIND); /* 65 - Sending the data requires a rewind + that failed */ + def(CURLE_SSL_ENGINE_INITFAILED); /* 66 - failed to initialise ENGINE */ + def(CURLE_LOGIN_DENIED); /* 67 - user); password or similar was not + accepted and we failed to login */ + def(CURLE_TFTP_NOTFOUND); /* 68 - file not found on server */ + def(CURLE_TFTP_PERM); /* 69 - permission problem on server */ + def(CURLE_TFTP_DISKFULL); /* 70 - out of disk space on server */ + def(CURLE_TFTP_ILLEGAL); /* 71 - Illegal TFTP operation */ + def(CURLE_TFTP_UNKNOWNID); /* 72 - Unknown transfer ID */ + def(CURLE_TFTP_EXISTS); /* 73 - File already exists */ + def(CURLE_TFTP_NOSUCHUSER); /* 74 - No such user */ + def(CURLE_CONV_FAILED); /* 75 - conversion failed */ + def(CURLE_CONV_REQD); /* 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION); + CURLOPT_CONV_TO_NETWORK_FUNCTION); and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + def(CURLE_SSL_CACERT_BADFILE); /* 77 - could not load CACERT file); missing + or wrong format */ + def(CURLE_REMOTE_FILE_NOT_FOUND); /* 78 - remote file not found */ + def(CURLE_SSH); /* 79 - error from the SSH layer); somewhat + generic so the error message will be of + interest when this has happened */ + + def(CURLE_SSL_SHUTDOWN_FAILED); /* 80 - Failed to shut down the SSL + connection */ +#undef def + } +}; + +static const CURLcodeMapper sCURLcodeMapper; + +LLXMLRPCListener::LLXMLRPCListener(const std::string& pumpname): + mBoundListener(LLEventPumps::instance(). + obtain(pumpname). + listen("LLXMLRPCListener", boost::bind(&LLXMLRPCListener::process, this, _1))) +{ +} + +/** + * Capture an outstanding LLXMLRPCTransaction and poll it periodically until + * done. + * + * The sequence is: + * # Instantiate Poller, which instantiates, populates and initiates an + * LLXMLRPCTransaction. Poller self-registers on the LLEventPump named + * "mainloop". + * # "mainloop" is conventionally pumped once per frame. On each such call, + * Poller checks its LLXMLRPCTransaction for completion. + * # When the LLXMLRPCTransaction completes, Poller collects results (if any) + * and sends notification. + * # The tricky part: Poller frees itself (and thus its LLXMLRPCTransaction) + * when done. The only external reference to it is the connection to the + * "mainloop" LLEventPump. + */ +class Poller +{ +public: + /// Validate the passed request for required fields, then use it to + /// populate an XMLRPC_REQUEST and an associated LLXMLRPCTransaction. Send + /// the request. + Poller(const LLSD& command): + mUri(command["uri"]), + mMethod(command["method"]), + mReplyPump(command["reply"]) + { + // LL_ERRS if any of these are missing + const char* required[] = { "uri", "method", "reply" }; + // optional: "options" (array of string) + // Validate the request + std::set missing; + for (const char** ri = boost::begin(required); ri != boost::end(required); ++ri) + { + // If the command does not contain this required entry, add it to 'missing'. + if (! command.has(*ri)) + { + missing.insert(*ri); + } + } + if (! missing.empty()) + { + LL_ERRS("LLXMLRPCListener") << mMethod << " request missing params: "; + const char* separator = ""; + for (std::set::const_iterator mi(missing.begin()), mend(missing.end()); + mi != mend; ++mi) + { + LL_CONT << separator << *mi; + separator = ", "; + } + LL_CONT << LL_ENDL; + } + + // Build the XMLRPC request. + XMLRPC_REQUEST request = XMLRPC_RequestNew(); + XMLRPC_RequestSetMethodName(request, mMethod.c_str()); + XMLRPC_RequestSetRequestType(request, xmlrpc_request_call); + XMLRPC_VALUE xparams = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct); + LLSD params(command["params"]); + if (params.isMap()) + { + for (LLSD::map_const_iterator pi(params.beginMap()), pend(params.endMap()); + pi != pend; ++pi) + { + std::string name(pi->first); + LLSD param(pi->second); + if (param.isString()) + { + XMLRPC_VectorAppendString(xparams, name.c_str(), param.asString().c_str(), 0); + } + else if (param.isInteger() || param.isBoolean()) + { + XMLRPC_VectorAppendInt(xparams, name.c_str(), param.asInteger()); + } + else if (param.isReal()) + { + XMLRPC_VectorAppendDouble(xparams, name.c_str(), param.asReal()); + } + else + { + LL_ERRS("LLXMLRPCListener") << mMethod << " request param " + << name << " has unknown type: " << param << LL_ENDL; + } + } + } + LLSD options(command["options"]); + if (options.isArray()) + { + XMLRPC_VALUE xoptions = XMLRPC_CreateVector("options", xmlrpc_vector_array); + for (LLSD::array_const_iterator oi(options.beginArray()), oend(options.endArray()); + oi != oend; ++oi) + { + XMLRPC_VectorAppendString(xoptions, NULL, oi->asString().c_str(), 0); + } + XMLRPC_AddValueToVector(xparams, xoptions); + } + XMLRPC_RequestSetData(request, xparams); + + mTransaction.reset(new LLXMLRPCTransaction(mUri, request)); + mPreviousStatus = mTransaction->status(NULL); + + // Free the XMLRPC_REQUEST object and the attached data values. + XMLRPC_RequestFree(request, 1); + + // Now ensure that we get regular callbacks to poll for completion. + mBoundListener = + LLEventPumps::instance(). + obtain("mainloop"). + listen(LLEventPump::inventName(), boost::bind(&Poller::poll, this, _1)); + + LL_INFOS("LLXMLRPCListener") << mMethod << " request sent to " << mUri << LL_ENDL; + } + + /// called by "mainloop" LLEventPump + bool poll(const LLSD&) + { + bool done = mTransaction->process(); + + CURLcode curlcode; + LLXMLRPCTransaction::Status status; + { + // LLXMLRPCTransaction::status() is defined to accept int* rather + // than CURLcode*. I don't feel the urge to fix the signature, but + // we want a CURLcode rather than an int. So fetch it as a local + // int, but then assign to a CURLcode for the remainder of this + // method. + int curlint; + status = mTransaction->status(&curlint); + curlcode = CURLcode(curlint); + } + + LLSD data; + data["status"] = sStatusMapper.lookup(status); + data["errorcode"] = sCURLcodeMapper.lookup(curlcode); + data["error"] = ""; + data["transfer_rate"] = 0.0; + LLEventPump& replyPump(LLEventPumps::instance().obtain(mReplyPump)); + if (! done) + { + // Not done yet, carry on. + if (status == LLXMLRPCTransaction::StatusDownloading + && status != mPreviousStatus) + { + // If a response has been received, send the + // 'downloading' status if it hasn't been sent. + replyPump.post(data); + } + + mPreviousStatus = status; + return false; + } + + // Here the transaction is complete. Check status. + data["error"] = mTransaction->statusMessage(); + data["transfer_rate"] = mTransaction->transferRate(); + LL_INFOS("LLXMLRPCListener") << mMethod << " result from " << mUri << ": status " + << data["status"].asString() << ", errorcode " + << data["errorcode"].asString() + << " (" << data["error"].asString() << ")" + << LL_ENDL; + // In addition to CURLE_OK, LLUserAuth distinguishes different error + // values of 'curlcode': + // CURLE_COULDNT_RESOLVE_HOST, + // CURLE_SSL_PEER_CERTIFICATE, + // CURLE_SSL_CACERT, + // CURLE_SSL_CONNECT_ERROR. + // Given 'message', need we care? + if (status == LLXMLRPCTransaction::StatusComplete) + { + // Success! Parse data. + std::string status_string(data["status"]); + data["responses"] = parseResponse(status_string); + data["status"] = status_string; + } + + // whether successful or not, send reply on requested LLEventPump + replyPump.post(data); + + // Because mTransaction is a boost::scoped_ptr, deleting this object + // frees our LLXMLRPCTransaction object. + // Because mBoundListener is an LLTempBoundListener, deleting this + // object disconnects it from "mainloop". + // *** MUST BE LAST *** + delete this; + return false; + } + +private: + /// Derived from LLUserAuth::parseResponse() and parseOptionInto() + LLSD parseResponse(std::string& status_string) + { + // Extract every member into data["responses"] (a map of string + // values). + XMLRPC_REQUEST response = mTransaction->response(); + if (! response) + { + LL_DEBUGS("LLXMLRPCListener") << "No response" << LL_ENDL; + return LLSD(); + } + + XMLRPC_VALUE param = XMLRPC_RequestGetData(response); + if (! param) + { + LL_DEBUGS("LLXMLRPCListener") << "Response contains no data" << LL_ENDL; + return LLSD(); + } + + // Now, parse everything + return parseValues(status_string, "", param); + } + + /** + * Parse key/value pairs from a given XMLRPC_VALUE into an LLSD map. + * @param key_pfx Used to describe a given key in log messages. At top + * level, pass "". When parsing an options array, pass the top-level key + * name of the array plus the index of the array entry; to this we'll + * append the subkey of interest. + * @param param XMLRPC_VALUE iterator. At top level, pass + * XMLRPC_RequestGetData(XMLRPC_REQUEST). + */ + LLSD parseValues(std::string& status_string, const std::string& key_pfx, XMLRPC_VALUE param) + { + LLSD responses; + for (XMLRPC_VALUE current = XMLRPC_VectorRewind(param); current; + current = XMLRPC_VectorNext(param)) + { + std::string key(XMLRPC_GetValueID(current)); + LL_DEBUGS("LLXMLRPCListener") << "key: " << key_pfx << key << LL_ENDL; + XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(current); + if (xmlrpc_type_string == type) + { + LLSD::String val(XMLRPC_GetValueString(current)); + LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL; + responses.insert(key, val); + } + else if (xmlrpc_type_int == type) + { + LLSD::Integer val(XMLRPC_GetValueInt(current)); + LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL; + responses.insert(key, val); + } + else if (xmlrpc_type_double == type) + { + LLSD::Real val(XMLRPC_GetValueDouble(current)); + LL_DEBUGS("LLXMLRPCListener") << "val: " << val << LL_ENDL; + responses.insert(key, val); + } + else if (xmlrpc_type_array == type) + { + // We expect this to be an array of submaps. Walk the array, + // recursively parsing each submap and collecting them. + LLSD array; + int i = 0; // for descriptive purposes + for (XMLRPC_VALUE row = XMLRPC_VectorRewind(current); row; + row = XMLRPC_VectorNext(current), ++i) + { + // Recursive call. For the lower-level key_pfx, if 'key' + // is "foo", pass "foo[0]:", then "foo[1]:", etc. In the + // nested call, a subkey "bar" will then be logged as + // "foo[0]:bar", and so forth. + // Parse the scalar subkey/value pairs from this array + // entry into a temp submap. Collect such submaps in 'array'. + array.append(parseValues(status_string, + STRINGIZE(key_pfx << key << '[' << i << "]:"), + row)); + } + // Having collected an 'array' of 'submap's, insert that whole + // 'array' as the value of this 'key'. + responses.insert(key, array); + } + else + { + // whoops - unrecognized type + LL_WARNS("LLXMLRPCListener") << "Unhandled xmlrpc type " << type << " for key " + << key_pfx << key << LL_ENDL; + responses.insert(key, STRINGIZE("')); + status_string = "BadType"; + } + } + return responses; + } + + const std::string mUri; + const std::string mMethod; + const std::string mReplyPump; + LLTempBoundListener mBoundListener; + boost::scoped_ptr mTransaction; + LLXMLRPCTransaction::Status mPreviousStatus; // To detect state changes. +}; + +bool LLXMLRPCListener::process(const LLSD& command) +{ + // Allocate a new heap Poller, but do not save a pointer to it. Poller + // will check its own status and free itself on completion of the request. + (new Poller(command)); + // conventional event listener return + return false; +} diff --git a/indra/newview/llxmlrpclistener.h b/indra/newview/llxmlrpclistener.h new file mode 100644 index 0000000000..120c2b329b --- /dev/null +++ b/indra/newview/llxmlrpclistener.h @@ -0,0 +1,35 @@ +/** + * @file llxmlrpclistener.h + * @author Nat Goodspeed + * @date 2009-03-18 + * @brief LLEventPump API for LLXMLRPCTransaction. This header doesn't + * actually define the API; the API is defined by the pump name on + * which this class listens, and by the expected content of LLSD it + * receives. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLXMLRPCLISTENER_H) +#define LL_LLXMLRPCLISTENER_H + +#include "llevents.h" + +/// Listen on an LLEventPump with specified name for LLXMLRPCTransaction +/// request events. +class LLXMLRPCListener +{ +public: + /// Specify the pump name on which to listen + LLXMLRPCListener(const std::string& pumpname); + + /// Handle request events on the event pump specified at construction time + bool process(const LLSD& command); + +private: + LLTempBoundListener mBoundListener; +}; + +#endif /* ! defined(LL_LLXMLRPCLISTENER_H) */ diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp index a2fd0f0d9c..0e1beb377f 100644 --- a/indra/newview/llxmlrpctransaction.cpp +++ b/indra/newview/llxmlrpctransaction.cpp @@ -33,6 +33,7 @@ #include "llviewerprecompiledheaders.h" #include "llxmlrpctransaction.h" +#include "llxmlrpclistener.h" #include "llcurl.h" #include "llviewercontrol.h" @@ -42,6 +43,13 @@ #include "llappviewer.h" +// Static instance of LLXMLRPCListener declared here so that every time we +// bring in this code, we instantiate a listener. If we put the static +// instance of LLXMLRPCListener into llxmlrpclistener.cpp, the linker would +// simply omit llxmlrpclistener.o, and shouting on the LLEventPump would do +// nothing. +static LLXMLRPCListener listener("LLXMLRPCTransaction"); + LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const { return LLXMLRPCValue(XMLRPC_VectorGetValueWithID(mV, id)); @@ -213,6 +221,11 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri, XMLRPC_RequestSetData(request, params.getValue()); init(request, useGzip); + // DEV-28398: without this XMLRPC_RequestFree() call, it looks as though + // the 'request' object is simply leaked. It's less clear to me whether we + // should also ask to free request value data (second param 1), since the + // data come from 'params'. + XMLRPC_RequestFree(request, 1); } diff --git a/indra/newview/tests/llcapabilitylistener_test.cpp b/indra/newview/tests/llcapabilitylistener_test.cpp index 3c5f6fad2d..90cc867852 100644 --- a/indra/newview/tests/llcapabilitylistener_test.cpp +++ b/indra/newview/tests/llcapabilitylistener_test.cpp @@ -24,9 +24,9 @@ #include "../test/lltut.h" #include "../llcapabilityprovider.h" #include "lluuid.h" -#include "llerrorcontrol.h" #include "tests/networkio.h" #include "tests/commtest.h" +#include "tests/wrapllerrs.h" #include "stringize.h" #if defined(LL_WINDOWS) @@ -104,28 +104,6 @@ namespace tut typedef llcapears_group::object llcapears_object; llcapears_group llsdmgr("llcapabilitylistener"); - struct CaptureError: public LLError::OverrideFatalFunction - { - CaptureError(): - LLError::OverrideFatalFunction(boost::bind(&CaptureError::operator(), this, _1)) - { - LLError::setPrintLocation(false); - } - - struct FatalException: public std::runtime_error - { - FatalException(const std::string& what): std::runtime_error(what) {} - }; - - void operator()(const std::string& message) - { - error = message; - throw FatalException(message); - } - - std::string error; - }; - template<> template<> void llcapears_object::test<1>() { @@ -137,10 +115,10 @@ namespace tut std::string threw; try { - CaptureError capture; + WrapLL_ERRS capture; regionPump.post(request); } - catch (const CaptureError::FatalException& e) + catch (const WrapLL_ERRS::FatalException& e) { threw = e.what(); } @@ -184,10 +162,10 @@ namespace tut std::string threw; try { - CaptureError capture; + WrapLL_ERRS capture; regionPump.post(request); } - catch (const CaptureError::FatalException& e) + catch (const WrapLL_ERRS::FatalException& e) { threw = e.what(); } @@ -246,10 +224,10 @@ namespace tut std::string threw; try { - CaptureError capture; + WrapLL_ERRS capture; regionPump.post(request); } - catch (const CaptureError::FatalException& e) + catch (const WrapLL_ERRS::FatalException& e) { threw = e.what(); } diff --git a/indra/newview/tests/llxmlrpclistener_test.cpp b/indra/newview/tests/llxmlrpclistener_test.cpp new file mode 100644 index 0000000000..0c1ee42ffc --- /dev/null +++ b/indra/newview/tests/llxmlrpclistener_test.cpp @@ -0,0 +1,230 @@ +/* + * @file llxmlrpclistener_test.cpp + * @author Nat Goodspeed + * @date 2009-03-20 + * @brief Test for llxmlrpclistener. + * + * $LicenseInfo:firstyear=2009&license=internal$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "../llviewerprecompiledheaders.h" +// associated header +#include "../llxmlrpclistener.h" +// STL headers +#include +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "../llxmlrpctransaction.h" +#include "llevents.h" +#include "lleventfilter.h" +#include "llsd.h" +#include "llcontrol.h" +#include "tests/wrapllerrs.h" + +LLControlGroup gSavedSettings; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct data + { + data(): + pumps(LLEventPumps::instance()), + uri("http://127.0.0.1:8000") + { + // These variables are required by machinery used by + // LLXMLRPCTransaction. The values reflect reality for this test + // executable; hopefully these values are correct. + gSavedSettings.declareBOOL("BrowserProxyEnabled", FALSE, "", FALSE); // don't persist + gSavedSettings.declareBOOL("NoVerifySSLCert", TRUE, "", FALSE); // don't persist + } + + // LLEventPump listener signature + bool captureReply(const LLSD& r) + { + reply = r; + return false; + } + + LLSD reply; + LLEventPumps& pumps; + std::string uri; + }; + typedef test_group llxmlrpclistener_group; + typedef llxmlrpclistener_group::object object; + llxmlrpclistener_group llxmlrpclistenergrp("llxmlrpclistener"); + + template<> template<> + void object::test<1>() + { + set_test_name("request validation"); + WrapLL_ERRS capture; + LLSD request; + request["uri"] = uri; + std::string threw; + try + { + pumps.obtain("LLXMLRPCTransaction").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("threw exception", threw, "missing params"); + ensure_contains("identified missing", threw, "method"); + ensure_contains("identified missing", threw, "reply"); + } + + template<> template<> + void object::test<2>() + { + set_test_name("param types validation"); + WrapLL_ERRS capture; + LLSD request; + request["uri"] = uri; + request["method"] = "hello"; + request["reply"] = "reply"; + LLSD& params(request["params"]); + params["who"]["specifically"] = "world"; // LLXMLRPCListener only handles scalar params + std::string threw; + try + { + pumps.obtain("LLXMLRPCTransaction").post(request); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("threw exception", threw, "unknown type"); + } + + template<> template<> + void object::test<3>() + { + set_test_name("success case"); + LLSD request; + request["uri"] = uri; + request["method"] = "hello"; + request["reply"] = "reply"; + LLSD& params(request["params"]); + params["who"] = "world"; + // Set up a timeout filter so we don't spin forever waiting. + LLEventTimeout watchdog; + // Connect the timeout filter to the reply pump. + LLTempBoundListener temp( + pumps.obtain("reply"). + listen("watchdog", boost::bind(&LLEventTimeout::post, boost::ref(watchdog), _1))); + // Now connect our target listener to the timeout filter. + watchdog.listen("captureReply", boost::bind(&data::captureReply, this, _1)); + // Kick off the request... + reply.clear(); + pumps.obtain("LLXMLRPCTransaction").post(request); + // Set the timer + F32 timeout(10); + watchdog.eventAfter(timeout, LLSD().insert("timeout", 0)); + // and pump "mainloop" until we get something, whether from + // LLXMLRPCListener or from the watchdog filter. + LLTimer timer; + F32 start = timer.getElapsedTimeF32(); + LLEventPump& mainloop(pumps.obtain("mainloop")); + while (reply.isUndefined()) + { + mainloop.post(LLSD()); + } + ensure("timeout works", (timer.getElapsedTimeF32() - start) < (timeout + 1)); + ensure_equals(reply["responses"]["hi_there"].asString(), "Hello, world!"); + } + + template<> template<> + void object::test<4>() + { + set_test_name("bogus method"); + LLSD request; + request["uri"] = uri; + request["method"] = "goodbye"; + request["reply"] = "reply"; + LLSD& params(request["params"]); + params["who"] = "world"; + // Set up a timeout filter so we don't spin forever waiting. + LLEventTimeout watchdog; + // Connect the timeout filter to the reply pump. + LLTempBoundListener temp( + pumps.obtain("reply"). + listen("watchdog", boost::bind(&LLEventTimeout::post, boost::ref(watchdog), _1))); + // Now connect our target listener to the timeout filter. + watchdog.listen("captureReply", boost::bind(&data::captureReply, this, _1)); + // Kick off the request... + reply.clear(); + pumps.obtain("LLXMLRPCTransaction").post(request); + // Set the timer + F32 timeout(10); + watchdog.eventAfter(timeout, LLSD().insert("timeout", 0)); + // and pump "mainloop" until we get something, whether from + // LLXMLRPCListener or from the watchdog filter. + LLTimer timer; + F32 start = timer.getElapsedTimeF32(); + LLEventPump& mainloop(pumps.obtain("mainloop")); + while (reply.isUndefined()) + { + mainloop.post(LLSD()); + } + ensure("timeout works", (timer.getElapsedTimeF32() - start) < (timeout + 1)); + ensure_equals("XMLRPC error", reply["status"].asString(), "XMLRPCError"); + } + + template<> template<> + void object::test<5>() + { + set_test_name("bad type"); + LLSD request; + request["uri"] = uri; + request["method"] = "getdict"; + request["reply"] = "reply"; + (void)request["params"]; + // Set up a timeout filter so we don't spin forever waiting. + LLEventTimeout watchdog; + // Connect the timeout filter to the reply pump. + LLTempBoundListener temp( + pumps.obtain("reply"). + listen("watchdog", boost::bind(&LLEventTimeout::post, boost::ref(watchdog), _1))); + // Now connect our target listener to the timeout filter. + watchdog.listen("captureReply", boost::bind(&data::captureReply, this, _1)); + // Kick off the request... + reply.clear(); + pumps.obtain("LLXMLRPCTransaction").post(request); + // Set the timer + F32 timeout(10); + watchdog.eventAfter(timeout, LLSD().insert("timeout", 0)); + // and pump "mainloop" until we get something, whether from + // LLXMLRPCListener or from the watchdog filter. + LLTimer timer; + F32 start = timer.getElapsedTimeF32(); + LLEventPump& mainloop(pumps.obtain("mainloop")); + while (reply.isUndefined()) + { + mainloop.post(LLSD()); + } + ensure("timeout works", (timer.getElapsedTimeF32() - start) < (timeout + 1)); + ensure_equals(reply["status"].asString(), "BadType"); + ensure_contains("bad type", reply["responses"]["nested_dict"].asString(), "bad XMLRPC type"); + } +} // namespace tut + +/***************************************************************************** +* Resolve link errors: use real machinery here, since we intend to exchange +* actual XML with a peer process. +*****************************************************************************/ +// Including llxmlrpctransaction.cpp drags in the static LLXMLRPCListener +// instantiated there. That's why it works to post requests to the LLEventPump +// named "LLXMLRPCTransaction". +#include "../llxmlrpctransaction.cpp" +#include "llcontrol.cpp" +#include "llxmltree.cpp" +#include "llxmlparser.cpp" diff --git a/indra/newview/tests/test_llxmlrpc_peer.py b/indra/newview/tests/test_llxmlrpc_peer.py new file mode 100644 index 0000000000..cb8f7d26c4 --- /dev/null +++ b/indra/newview/tests/test_llxmlrpc_peer.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +"""\ +@file test_llxmlrpc_peer.py +@author Nat Goodspeed +@date 2008-10-09 +@brief This script asynchronously runs the executable (with args) specified on + the command line, returning its result code. While that executable is + running, we provide dummy local services for use by C++ tests. + +$LicenseInfo:firstyear=2008&license=viewergpl$ +Copyright (c) 2008, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +import sys +from threading import Thread +from SimpleXMLRPCServer import SimpleXMLRPCServer + +mydir = os.path.dirname(__file__) # expected to be .../indra/newview/tests/ +sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) +sys.path.insert(1, os.path.join(mydir, os.pardir, os.pardir, "llmessage", "tests")) +from testrunner import run, debug + +class TestServer(SimpleXMLRPCServer): + def _dispatch(self, method, params): + try: + func = getattr(self, method) + except AttributeError: + raise Exception('method "%s" is not supported' % method) + else: + # LLXMLRPCListener constructs XMLRPC parameters that arrive as a + # 1-tuple containing a dict. + return func(**(params[0])) + + def hello(self, who): + # LLXMLRPCListener expects a dict return. + return {"hi_there": "Hello, %s!" % who} + + def getdict(self): + return dict(nested_dict=dict(a=17, b=5)) + + def log_request(self, code, size=None): + # For present purposes, we don't want the request splattered onto + # stderr, as it would upset devs watching the test run + pass + + def log_error(self, format, *args): + # Suppress error output as well + pass + +class ServerRunner(Thread): + def run(self): + server = TestServer(('127.0.0.1', 8000)) + debug("Starting XMLRPC server...\n") + server.serve_forever() + +if __name__ == "__main__": + sys.exit(run(server=ServerRunner(name="xmlrpc"), *sys.argv[1:])) diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index e401f89b22..31130c3c79 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -32,96 +32,10 @@ // other Linden headers #include "lltut.h" #include "stringize.h" +#include "tests/listener.h" using boost::assign::list_of; -/***************************************************************************** -* test listener class -*****************************************************************************/ -class Listener; -std::ostream& operator<<(std::ostream&, const Listener&); - -class Listener -{ -public: - Listener(const std::string& name): - mName(name) - { -// std::cout << *this << ": ctor\n"; - } - Listener(const Listener& that): - mName(that.mName), - mLastEvent(that.mLastEvent) - { -// std::cout << *this << ": copy\n"; - } - virtual ~Listener() - { -// std::cout << *this << ": dtor\n"; - } - std::string getName() const { return mName; } - bool call(const LLSD& event) - { -// std::cout << *this << "::call(" << event << ")\n"; - mLastEvent = event; - return false; - } - bool callstop(const LLSD& event) - { -// std::cout << *this << "::callstop(" << event << ")\n"; - mLastEvent = event; - return true; - } - LLSD getLastEvent() const - { -// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; - return mLastEvent; - } - void reset(const LLSD& to = LLSD()) - { -// std::cout << *this << "::reset(" << to << ")\n"; - mLastEvent = to; - } - -private: - std::string mName; - LLSD mLastEvent; -}; - -std::ostream& operator<<(std::ostream& out, const Listener& listener) -{ - out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; - return out; -} - -struct Collect -{ - bool add(const std::string& bound, const LLSD& event) - { - result.push_back(bound); - return false; - } - void clear() { result.clear(); } - typedef std::vector StringList; - StringList result; -}; - -std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) -{ - out << '('; - Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); - if (begin != end) - { - out << '"' << *begin << '"'; - while (++begin != end) - { - out << ", \"" << *begin << '"'; - } - } - out << ')'; - return out; -} - template T make(const T& value) { return value; } @@ -174,14 +88,7 @@ namespace tut // default combiner is defined to return the value returned by the // last listener, which is meaningless if there were no listeners. per_frame.post(0); - // NOTE: boost::bind() saves its arguments by VALUE! If you pass an - // object instance rather than a pointer, you'll end up binding to an - // internal copy of that instance! Use boost::ref() to capture a - // reference instead. - LLBoundListener connection = per_frame.listen(listener0.getName(), - boost::bind(&Listener::call, - boost::ref(listener0), - _1)); + LLBoundListener connection = listener0.listenTo(per_frame); ensure("connected", connection.connected()); ensure("not blocked", ! connection.blocked()); per_frame.post(1); @@ -207,6 +114,10 @@ namespace tut bool threw = false; try { + // NOTE: boost::bind() saves its arguments by VALUE! If you pass + // an object instance rather than a pointer, you'll end up binding + // to an internal copy of that instance! Use boost::ref() to + // capture a reference instead. per_frame.listen(listener0.getName(), // note bug, dup name boost::bind(&Listener::call, boost::ref(listener1), _1)); } @@ -221,8 +132,7 @@ namespace tut } ensure("threw DupListenerName", threw); // do it right this time - per_frame.listen(listener1.getName(), - boost::bind(&Listener::call, boost::ref(listener1), _1)); + listener1.listenTo(per_frame); per_frame.post(5); check_listener("got", listener0, 5); check_listener("got", listener1, 5); @@ -252,16 +162,10 @@ namespace tut LLEventPump& per_frame(pumps.obtain("per-frame")); listener0.reset(0); listener1.reset(0); - LLBoundListener bound0 = per_frame.listen(listener0.getName(), - boost::bind(&Listener::callstop, - boost::ref(listener0), - _1)); - LLBoundListener bound1 = per_frame.listen(listener1.getName(), - boost::bind(&Listener::call, - boost::ref(listener1), - _1), - // after listener0 - make(list_of(listener0.getName()))); + LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop); + LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call, + // after listener0 + make(list_of(listener0.getName()))); ensure("enabled", per_frame.enabled()); ensure("connected 0", bound0.connected()); ensure("unblocked 0", ! bound0.blocked()); @@ -301,7 +205,7 @@ namespace tut // LLEventQueue. LLEventPump& mainloop(pumps.obtain("mainloop")); ensure("LLEventQueue leaf class", dynamic_cast(&login)); - login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.listenTo(login); listener0.reset(0); login.post(1); check_listener("waiting for queued event", listener0, 0); @@ -354,11 +258,10 @@ namespace tut { set_test_name("stopListening()"); LLEventPump& login(pumps.obtain("login")); - login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.listenTo(login); login.stopListening(listener0.getName()); // should not throw because stopListening() should have removed name - login.listen(listener0.getName(), - boost::bind(&Listener::callstop, boost::ref(listener0), _1)); + listener0.listenTo(login, &Listener::callstop); LLBoundListener wrong = login.getListener("bogus"); ensure("bogus connection disconnected", ! wrong.connected()); ensure("bogus connection blocked", wrong.blocked()); @@ -378,10 +281,8 @@ namespace tut boost::bind(&LLEventPump::post, boost::ref(filter0), _1)); upstream.listen(filter1.getName(), boost::bind(&LLEventPump::post, boost::ref(filter1), _1)); - filter0.listen(listener0.getName(), - boost::bind(&Listener::call, boost::ref(listener0), _1)); - filter1.listen(listener1.getName(), - boost::bind(&Listener::call, boost::ref(listener1), _1)); + listener0.listenTo(filter0); + listener1.listenTo(filter1); listener0.reset(0); listener1.reset(0); upstream.post(1); @@ -536,7 +437,7 @@ namespace tut // Passing a string LLEventPump name to LLListenerOrPumpName listener0.reset(0); LLEventStream random("random"); - random.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); + listener0.listenTo(random); eventSource("random"); check_listener("got by pump name", listener0, 17); bool threw = false; diff --git a/indra/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp index 0c4bbc2e62..093a29652c 100644 --- a/indra/test/llsdutil_tut.cpp +++ b/indra/test/llsdutil_tut.cpp @@ -44,12 +44,40 @@ #include "v4math.h" #include "llquaternion.h" #include "llsdutil.h" - +#include +#include namespace tut { struct llsdutil_data { + void test_matches(const std::string& proto_key, const LLSD& possibles, + const char** begin, const char** end) + { + std::set succeed(begin, end); + LLSD prototype(possibles[proto_key]); + for (LLSD::map_const_iterator pi(possibles.beginMap()), pend(possibles.endMap()); + pi != pend; ++pi) + { + std::string match(llsd_matches(prototype, pi->second)); + std::set::const_iterator found = succeed.find(pi->first); + if (found != succeed.end()) + { + // This test is supposed to succeed. Comparing to the + // empty string ensures that if the test fails, it will + // display the string received so we can tell what failed. + ensure_equals("match", match, ""); + } + else + { + // This test is supposed to fail. If we get a false match, + // the string 'match' will be empty, which doesn't tell us + // much about which case went awry. So construct a more + // detailed description string. + ensure(proto_key + " shouldn't match " + pi->first, ! match.empty()); + } + } + } }; typedef test_group llsdutil_test;; typedef llsdutil_test::object llsdutil_object; @@ -159,4 +187,154 @@ namespace tut LLSD sd1 = ll_sd_from_color4(c1); ensure_equals("sd -> LLColor4 -> sd", sd, sd1); } + + template<> template<> + void llsdutil_object::test<9>() + { + set_test_name("llsd_matches"); + + // for this test, construct a map of all possible LLSD types + LLSD map; + map.insert("empty", LLSD()); + map.insert("Boolean", LLSD::Boolean()); + map.insert("Integer", LLSD::Integer(0)); + map.insert("Real", LLSD::Real(0.0)); + map.insert("String", LLSD::String("bah")); + map.insert("NumString", LLSD::String("1")); + map.insert("UUID", LLSD::UUID()); + map.insert("Date", LLSD::Date()); + map.insert("URI", LLSD::URI()); + map.insert("Binary", LLSD::Binary()); + map.insert("Map", LLSD().insert("foo", LLSD())); + // array can't be constructed on the fly + LLSD array; + array.append(LLSD()); + map.insert("Array", array); + + // These iterators are declared outside our various for loops to avoid + // fatal MSVC warning: "I used to be broken, but I'm all better now!" + LLSD::map_const_iterator mi(map.beginMap()), mend(map.endMap()); + + // empty prototype matches anything + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure_equals(std::string("empty matches ") + mi->first, llsd_matches(LLSD(), mi->second), ""); + } + + LLSD proto_array, data_array; + for (int i = 0; i < 3; ++i) + { + proto_array.append(LLSD()); + data_array.append(LLSD()); + } + + // prototype array matches only array + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure(std::string("array doesn't match ") + mi->first, + ! llsd_matches(proto_array, mi->second).empty()); + } + + // data array must be at least as long as prototype array + proto_array.append(LLSD()); + ensure_equals("data array too short", llsd_matches(proto_array, data_array), + "Array size 4 required instead of Array size 3"); + data_array.append(LLSD()); + ensure_equals("data array just right", llsd_matches(proto_array, data_array), ""); + data_array.append(LLSD()); + ensure_equals("data array longer", llsd_matches(proto_array, data_array), ""); + + // array element matching + data_array[0] = LLSD::String(); + ensure_equals("undefined prototype array entry", llsd_matches(proto_array, data_array), ""); + proto_array[0] = LLSD::Binary(); + ensure_equals("scalar prototype array entry", llsd_matches(proto_array, data_array), + "[0]: Binary required instead of String"); + data_array[0] = LLSD::Binary(); + ensure_equals("matching prototype array entry", llsd_matches(proto_array, data_array), ""); + + // build a coupla maps + LLSD proto_map, data_map; + data_map["got"] = LLSD(); + data_map["found"] = LLSD(); + for (LLSD::map_const_iterator dmi(data_map.beginMap()), dmend(data_map.endMap()); + dmi != dmend; ++dmi) + { + proto_map[dmi->first] = dmi->second; + } + proto_map["foo"] = LLSD(); + proto_map["bar"] = LLSD(); + + // prototype map matches only map + for (mi = map.beginMap(); mi != mend; ++mi) + { + ensure(std::string("map doesn't match ") + mi->first, + ! llsd_matches(proto_map, mi->second).empty()); + } + + // data map must contain all keys in prototype map + std::string error(llsd_matches(proto_map, data_map)); + ensure_contains("missing keys", error, "missing keys"); + ensure_contains("missing foo", error, "foo"); + ensure_contains("missing bar", error, "bar"); + ensure_does_not_contain("found found", error, "found"); + ensure_does_not_contain("got got", error, "got"); + data_map["bar"] = LLSD(); + error = llsd_matches(proto_map, data_map); + ensure_contains("missing foo", error, "foo"); + ensure_does_not_contain("got bar", error, "bar"); + data_map["foo"] = LLSD(); + ensure_equals("data map just right", llsd_matches(proto_map, data_map), ""); + data_map["extra"] = LLSD(); + ensure_equals("data map with extra", llsd_matches(proto_map, data_map), ""); + + // map element matching + data_map["foo"] = LLSD::String(); + ensure_equals("undefined prototype map entry", llsd_matches(proto_map, data_map), ""); + proto_map["foo"] = LLSD::Binary(); + ensure_equals("scalar prototype map entry", llsd_matches(proto_map, data_map), + "['foo']: Binary required instead of String"); + data_map["foo"] = LLSD::Binary(); + ensure_equals("matching prototype map entry", llsd_matches(proto_map, data_map), ""); + + // String + { + static const char* matches[] = { "String", "NumString", "Boolean", "Integer", + "Real", "UUID", "Date", "URI" }; + test_matches("String", map, boost::begin(matches), boost::end(matches)); + } + + // Boolean, Integer, Real + static const char* numerics[] = { "Boolean", "Integer", "Real" }; + for (const char **ni = boost::begin(numerics), **nend = boost::end(numerics); + ni != nend; ++ni) + { + static const char* matches[] = { "Boolean", "Integer", "Real", "String", "NumString" }; + test_matches(*ni, map, boost::begin(matches), boost::end(matches)); + } + + // UUID + { + static const char* matches[] = { "UUID", "String", "NumString" }; + test_matches("UUID", map, boost::begin(matches), boost::end(matches)); + } + + // Date + { + static const char* matches[] = { "Date", "String", "NumString" }; + test_matches("Date", map, boost::begin(matches), boost::end(matches)); + } + + // URI + { + static const char* matches[] = { "URI", "String", "NumString" }; + test_matches("URI", map, boost::begin(matches), boost::end(matches)); + } + + // Binary + { + static const char* matches[] = { "Binary" }; + test_matches("Binary", map, boost::begin(matches), boost::end(matches)); + } + } } diff --git a/indra/test/lltut.cpp b/indra/test/lltut.cpp index 201e174f9c..e4e0de1ff1 100644 --- a/indra/test/lltut.cpp +++ b/indra/test/lltut.cpp @@ -76,9 +76,13 @@ namespace tut void ensure_equals(const char* m, const LLSD& actual, const LLSD& expected) + { + ensure_equals(std::string(m), actual, expected); + } + + void ensure_equals(const std::string& msg, const LLSD& actual, + const LLSD& expected) { - const std::string& msg = m ? m : ""; - ensure_equals(msg + " type", actual.type(), expected.type()); switch (actual.type()) { @@ -128,7 +132,7 @@ namespace tut { ensure_equals(msg + " map keys", actual_iter->first, expected_iter->first); - ensure_equals((msg + "[" + actual_iter->first + "]").c_str(), + ensure_equals(msg + "[" + actual_iter->first + "]", actual_iter->second, expected_iter->second); ++actual_iter; ++expected_iter; @@ -141,7 +145,7 @@ namespace tut for(int i = 0; i < actual.size(); ++i) { - ensure_equals((msg + llformat("[%d]", i)).c_str(), + ensure_equals(msg + llformat("[%d]", i), actual[i], expected[i]); } return; diff --git a/indra/test/lltut.h b/indra/test/lltut.h index 47ea9d3f9e..ba3791cbd4 100644 --- a/indra/test/lltut.h +++ b/indra/test/lltut.h @@ -121,6 +121,9 @@ namespace tut void ensure_equals(const char* msg, const LLSD& actual, const LLSD& expected); + + void ensure_equals(const std::string& msg, + const LLSD& actual, const LLSD& expected); void ensure_starts_with(const std::string& msg, const std::string& actual, const std::string& expectedStart); diff --git a/indra/test/test.cpp b/indra/test/test.cpp index ba81c6e49e..0ba5758e15 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -64,13 +64,14 @@ namespace tut class LLTestCallback : public tut::callback { public: - LLTestCallback(bool verbose_mode, std::ostream *stream) : + LLTestCallback(bool verbose_mode, std::ostream *stream, bool wait) : mVerboseMode(verbose_mode), mTotalTests(0), mPassedTests(0), mFailedTests(0), mSkippedTests(0), - mStream(stream) + mStream(stream), + mWaitAtExit(wait) { } @@ -137,6 +138,11 @@ public: } run_completed_(std::cout); + if(mWaitAtExit) { + std::cerr << "Waiting for input before exiting..." << std::endl; + std::cin.get(); + } + if (mFailedTests > 0) { exit(1); @@ -176,6 +182,7 @@ protected: int mFailedTests; int mSkippedTests; std::ostream *mStream; + bool mWaitAtExit; }; static const apr_getopt_option_t TEST_CL_OPTIONS[] = @@ -328,7 +335,7 @@ int main(int argc, char **argv) } // run the tests - LLTestCallback callback(verbose_mode, output); + LLTestCallback callback(verbose_mode, output, wait_at_exit); tut::runner.get().set_callback(&callback); if(test_group.empty()) @@ -339,12 +346,6 @@ int main(int argc, char **argv) { tut::runner.get().run_tests(test_group); } - - if (wait_at_exit) - { - std::cerr << "Waiting for input before exiting..." << std::endl; - std::cin.get(); - } if (output) { diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt new file mode 100644 index 0000000000..d5eea0d0b0 --- /dev/null +++ b/indra/viewer_components/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(login) diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt new file mode 100644 index 0000000000..434b58f5c7 --- /dev/null +++ b/indra/viewer_components/login/CMakeLists.txt @@ -0,0 +1,43 @@ +project(login) + +include(00-Common) +include(LLCommon) +include(LLMath) +include(LLXML) +include(Pth) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLXML_INCLUDE_DIRS} + ${PTH_INCLUDE_DIRS} + ) + +set(login_SOURCE_FILES + lllogin.cpp + ) + +set(login_HEADER_FILES + lllogin.h + ) + +set_source_files_properties(${login_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND + login_SOURCE_FILES + ${login_HEADER_FILES} + ) + +add_library(lllogin + ${login_SOURCE_FILES} + ) + +target_link_libraries(lllogin + ${LLCOMMON_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLXML_LIBRARIES} + ${PTH_LIBRARIES} + ) + +ADD_BUILD_TEST(lllogin lllogin "") diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp new file mode 100644 index 0000000000..7f2b27e64c --- /dev/null +++ b/indra/viewer_components/login/lllogin.cpp @@ -0,0 +1,383 @@ +/** + * @file lllogin.cpp + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include +#include "linden_common.h" +#include "llsd.h" +#include "llsdutil.h" + +/*==========================================================================*| +#ifdef LL_WINDOWS + // non-virtual destructor warning, boost::statechart does this intentionally. + #pragma warning (disable : 4265) +#endif +|*==========================================================================*/ + +#include "lllogin.h" + +#include +#include + +#include "llevents.h" +#include "lleventfilter.h" +#include "lleventcoro.h" + +//********************* +// LLLogin +// *NOTE:Mani - Is this Impl needed now that the state machine runs the show? +class LLLogin::Impl +{ +public: + Impl(): + mPump("login", true) // Create the module's event pump with a tweaked (unique) name. + { + mValidAuthResponse["status"] = LLSD(); + mValidAuthResponse["errorcode"] = LLSD(); + mValidAuthResponse["error"] = LLSD(); + mValidAuthResponse["transfer_rate"] = LLSD(); + } + + void connect(const std::string& uri, const LLSD& credentials); + void disconnect(); + LLEventPump& getEventPump() { return mPump; } + +private: + void sendProgressEvent(const std::string& desc, const LLSD& data = LLSD::emptyMap()) + { + LLSD status_data; + status_data["state"] = desc; + status_data["progress"] = 0.0f; + + if(mAuthResponse.has("transfer_rate")) + { + status_data["transfer_rate"] = mAuthResponse["transfer_rate"]; + } + + if(data.size() != 0) + { + status_data["data"] = data; + } + + mPump.post(status_data); + } + + LLSD validateResponse(const std::string& pumpName, const LLSD& response) + { + // Validate the response. If we don't recognize it, things + // could get ugly. + std::string mismatch(llsd_matches(mValidAuthResponse, response)); + if (! mismatch.empty()) + { + LL_ERRS("LLLogin") << "Received unrecognized event (" << mismatch << ") on " + << pumpName << "pump: " << response + << LL_ENDL; + return LLSD(); + } + + return response; + } + + typedef boost::coroutines::coroutine coroutine_type; + + void login_(coroutine_type::self& self, const std::string& uri, const LLSD& credentials); + + boost::scoped_ptr mCoro; + LLEventStream mPump; + LLSD mAuthResponse, mValidAuthResponse; +}; + +void LLLogin::Impl::connect(const std::string& uri, const LLSD& credentials) +{ + // If there's a previous coroutine instance, and that instance is still + // active, destroying the instance will terminate the coroutine by + // throwing an exception, thus unwinding the stack and destroying all + // local objects. It should (!) all Just Work. Nonetheless, it would be + // strange, so make a note of it. + if (mCoro && *mCoro) + { + LL_WARNS("LLLogin") << "Previous login attempt interrupted by new request" << LL_ENDL; + } + + // Construct a coroutine that will run our login_() method; placeholders + // forward the params from the (*mCoro)(etc.) call below. Using scoped_ptr + // ensures that if mCoro was already pointing to a previous instance, that + // old instance will be destroyed as noted above. + mCoro.reset(new coroutine_type(boost::bind(&Impl::login_, this, _1, _2, _3))); + // Run the coroutine until its first wait; at that point, return here. + (*mCoro)(std::nothrow, uri, credentials); + std::cout << "Here I am\n"; +} + +void LLLogin::Impl::login_(coroutine_type::self& self, + const std::string& uri, const LLSD& credentials) +{ + // Mimicking previous behavior, every time the OldSchoolLogin state + // machine arrived in the Offline state, it would send a progress + // announcement. + sendProgressEvent("offline", mAuthResponse["responses"]); + // Arriving in SRVRequest state + LLEventStream replyPump("reply", true); + // Should be an array of one or more uri strings. + LLSD rewrittenURIs; + { + LLEventTimeout filter(replyPump); + sendProgressEvent("srvrequest"); + + // Request SRV record. + LL_INFOS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL; + + // *NOTE:Mani - Completely arbitrary timeout value for SRV request. + filter.errorAfter(5, "SRV Request timed out!"); + + // Make request + LLSD request; + request["op"] = "rewriteURI"; + request["uri"] = uri; + request["reply"] = replyPump.getName(); + rewrittenURIs = postAndWait(self, request, "LLAres", filter); + } // we no longer need the filter + + LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction")); + + // Loop through the rewrittenURIs, counting attempts along the way. + // Because of possible redirect responses, we may make more than one + // attempt per rewrittenURIs entry. + LLSD::Integer attempts = 0; + for (LLSD::array_const_iterator urit(rewrittenURIs.beginArray()), + urend(rewrittenURIs.endArray()); + urit != urend; ++urit) + { + LLSD request(credentials); + request["reply"] = replyPump.getName(); + request["uri"] = *urit; + std::string status; + + // Loop back to here if login attempt redirects to a different + // request["uri"] + for (;;) + { + ++attempts; + LLSD progress_data; + progress_data["attempt"] = attempts; + progress_data["request"] = request; + sendProgressEvent("authenticating", progress_data); + + // We expect zero or more "Downloading" status events, followed by + // exactly one event with some other status. Use postAndWait() the + // first time, because -- at least in unit-test land -- it's + // possible for the reply to arrive before the post() call + // returns. Subsequent responses, of course, must be awaited + // without posting again. + for (mAuthResponse = validateResponse(replyPump.getName(), + postAndWait(self, request, xmlrpcPump, replyPump, "reply")); + mAuthResponse["status"].asString() == "Downloading"; + mAuthResponse = validateResponse(replyPump.getName(), + waitForEventOn(self, replyPump))) + { + // Still Downloading -- send progress update. + sendProgressEvent("downloading"); + } + status = mAuthResponse["status"].asString(); + + // Okay, we've received our final status event for this + // request. Unless we got a redirect response, break the retry + // loop for the current rewrittenURIs entry. + if (! (status == "Complete" && + mAuthResponse["responses"]["login"].asString() == "indeterminate")) + { + break; + } + + // Here the login service at the current URI is redirecting us + // to some other URI ("indeterminate" -- why not "redirect"?). + // The response should contain another uri to try, with its + // own auth method. + request["uri"] = mAuthResponse["next_url"]; + request["method"] = mAuthResponse["next_method"]; + } // loop back to try the redirected URI + + // Here we're done with redirects for the current rewrittenURIs + // entry. + if (status == "Complete") + { + // StatusComplete does not imply auth success. Check the + // actual outcome of the request. We've already handled the + // "indeterminate" case in the loop above. + sendProgressEvent((mAuthResponse["responses"]["login"].asString() == "true")? + "online" : "offline", + mAuthResponse["responses"]); + return; // Done! + } + // If we don't recognize status at all, trouble + if (! (status == "CURLError" + || status == "XMLRPCError" + || status == "OtherError")) + { + LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: " + << mAuthResponse << LL_ENDL; + return; + } + + // Here status IS one of the errors tested above. + } // Retry if there are any more rewrittenURIs. + + // Here we got through all the rewrittenURIs without succeeding. Tell + // caller this didn't work out so well. Of course, the only failure data + // we can reasonably show are from the last of the rewrittenURIs. + sendProgressEvent("offline", mAuthResponse["responses"]); +} + +void LLLogin::Impl::disconnect() +{ + sendProgressEvent("offline", mAuthResponse["responses"]); +} + +//********************* +// LLLogin +LLLogin::LLLogin() : + mImpl(new LLLogin::Impl()) +{ +} + +LLLogin::~LLLogin() +{ +} + +void LLLogin::connect(const std::string& uri, const LLSD& credentials) +{ + mImpl->connect(uri, credentials); +} + + +void LLLogin::disconnect() +{ + mImpl->disconnect(); +} + +LLEventPump& LLLogin::getEventPump() +{ + return mImpl->getEventPump(); +} + +// The following is the list of important functions that happen in the +// current login process that we want to move to this login module. + +// The list associates to event with the original idle_startup() 'STATE'. + +// Rewrite URIs + // State_LOGIN_AUTH_INIT +// Given a vector of login uris (usually just one), perform a dns lookup for the +// SRV record from each URI. I think this is used to distribute login requests to +// a single URI to multiple hosts. +// This is currently a synchronous action. (See LLSRV::rewriteURI() implementation) +// On dns lookup error the output uris == the input uris. +// +// Input: A vector of login uris +// Output: A vector of login uris +// +// Code: +// std::vector uris; +// LLViewerLogin::getInstance()->getLoginURIs(uris); +// std::vector::const_iterator iter, end; +// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter) +// { +// std::vector rewritten; +// rewritten = LLSRV::rewriteURI(*iter); +// sAuthUris.insert(sAuthUris.end(), +// rewritten.begin(), rewritten.end()); +// } +// sAuthUriNum = 0; + +// Authenticate +// STATE_LOGIN_AUTHENTICATE +// Connect to the login server, presumably login.cgi, requesting the login +// and a slew of related initial connection information. +// This is an asynch action. The final response, whether success or error +// is handled by STATE_LOGIN_PROCESS_REPONSE. +// There is no immediate error or output from this call. +// +// Input: +// URI +// Credentials (first, last, password) +// Start location +// Bool Flags: +// skip optional update +// accept terms of service +// accept critical message +// Last exec event. (crash state of previous session) +// requested optional data (inventory skel, initial outfit, etc.) +// local mac address +// viewer serial no. (md5 checksum?) + +//sAuthUriNum = llclamp(sAuthUriNum, 0, (S32)sAuthUris.size()-1); +//LLUserAuth::getInstance()->authenticate( +// sAuthUris[sAuthUriNum], +// auth_method, +// firstname, +// lastname, +// password, // web_login_key, +// start.str(), +// gSkipOptionalUpdate, +// gAcceptTOS, +// gAcceptCriticalMessage, +// gLastExecEvent, +// requested_options, +// hashed_mac_string, +// LLAppViewer::instance()->getSerialNumber()); + +// +// Download the Response +// STATE_LOGIN_NO_REPONSE_YET and STATE_LOGIN_DOWNLOADING +// I had assumed that this was default behavior of the message system. However... +// During login, the message system is checked only by these two states in idle_startup(). +// I guess this avoids the overhead of checking network messages for those login states +// that don't need to do so, but geez! +// There are two states to do this one function just to update the login +// status text from 'Logging In...' to 'Downloading...' +// + +// +// Handle Login Response +// STATE_LOGIN_PROCESS_RESPONSE +// +// This state handle the result of the request to login. There is a metric ton of +// code in this case. This state will transition to: +// STATE_WORLD_INIT, on success. +// STATE_AUTHENTICATE, on failure. +// STATE_UPDATE_CHECK, to handle user during login interaction like TOS display. +// +// Much of the code in this case belongs on the viewer side of the fence and not in login. +// Login should probably return with a couple of events, success and failure. +// Failure conditions can be specified in the events data pacet to allow the viewer +// to re-engauge login as is appropriate. (Or should there be multiple failure messages?) +// Success is returned with the data requested from the login. According to OGP specs +// there may be intermediate steps before reaching this result in future login +// implementations. diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h new file mode 100644 index 0000000000..0598b4e457 --- /dev/null +++ b/indra/viewer_components/login/lllogin.h @@ -0,0 +1,133 @@ +/** + * @file lllogin.h + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLLOGIN_H +#define LL_LLLOGIN_H + +#include + +class LLSD; +class LLEventPump; + +/** + * @class LLLogin + * @brief Class to encapsulate the action and state of grid login. + */ +class LLLogin +{ +public: + LLLogin(); + ~LLLogin(); + + /** + * Make a connection to a grid. + * @param uri The 'well known and published' authentication URL. + * @param credentials LLSD data that contians the credentials. + * *NOTE:Mani The credential data can vary depending upon the authentication + * method used. The current interface matches the values passed to + * the XMLRPC login request. + { + method : string, + first : string, + last : string, + passwd : string, + start : string, + skipoptional : bool, + agree_to_tos : bool, + read_critical : bool, + last_exec_event : int, + version : string, + channel : string, + mac : string, + id0 : string, + options : [ strings ] + } + + */ + void connect(const std::string& uri, const LLSD& credentials); + + /** + * Disconnect from a the current connection. + */ + void disconnect(); + + /** + * Retrieve the event pump from this login class. + */ + LLEventPump& getEventPump(); + + /* + Event API + + LLLogin will issue multiple events to it pump to indicate the + progression of states through login. The most important + states are "offline" and "online" which indicate auth failure + and auth success respectively. + + pump: login (tweaked) + These are the events posted to the 'login' + event pump from the login module. + { + state : string, // See below for the list of states. + progress : real // for progress bar. + data : LLSD // Dependent upon state. + } + + States for method 'login_to_simulator' + offline - set initially state and upon failure. data is the server response. + srvrequest - upon uri rewrite request. no data. + authenticating - upon auth request. data, 'attempt' number and 'request' llsd. + downloading - upon ack from auth server, before completion. no data + online - upon auth success. data is server response. + + + Dependencies: + pump: LLAres + LLLogin makes a request for a SRV record from the uri provided by the connect method. + The following event pump should exist to service that request. + pump name: LLAres + request = { + op : "rewriteURI" + uri : string + reply : string + + pump: LLXMLRPCListener + The request merely passes the credentials LLSD along, with one additional + member, 'reply', which is the string name of the event pump to reply on. + + */ + +private: + class Impl; + boost::scoped_ptr mImpl; +}; + +#endif // LL_LLLOGIN_H diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp new file mode 100644 index 0000000000..07c9db1099 --- /dev/null +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -0,0 +1,382 @@ +/** + * @file llviewerlogin_test.cpp + * @author Mark Palange + * @date 2009-02-26 + * @brief Tests of lllazy.h. + * + * $LicenseInfo:firstyear=2009&license=internal$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "../lllogin.h" +// STL headers +// std headers +#include +// external library headers +// other Linden headers +#include "llsd.h" +#include "../../../test/lltut.h" +#include "llevents.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +// This is a listener to receive results from lllogin. +class LoginListener +{ + std::string mName; + LLSD mLastEvent; +public: + LoginListener(const std::string& name) : + mName(name) + {} + + bool call(const LLSD& event) + { + std::cout << "LoginListener called!: " << event << std::endl; + mLastEvent = event; + return false; + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LoginListener::call, this, _1)); + } + + const LLSD& lastEvent() { return mLastEvent; } +}; + +class LLAresListener +{ + std::string mName; + LLSD mEvent; + bool mImmediateResponse; + bool mMultipleURIResponse; + +public: + LLAresListener(const std::string& name, + bool i = false, + bool m = false + ) : + mName(name), + mImmediateResponse(i), + mMultipleURIResponse(m) + {} + + bool handle_event(const LLSD& event) + { + std::cout << "LLAresListener called!: " << event << std::endl; + mEvent = event; + if(mImmediateResponse) + { + sendReply(); + } + return false; + } + + void sendReply() + { + if(mEvent["op"].asString() == "rewriteURI") + { + LLSD result; + if(mMultipleURIResponse) + { + result.append(LLSD("login.foo.com")); + } + result.append(mEvent["uri"]); + LLEventPumps::instance().obtain(mEvent["reply"]).post(result); + } + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LLAresListener::handle_event, this, _1)); + } +}; + +class LLXMLRPCListener +{ + std::string mName; + LLSD mEvent; + bool mImmediateResponse; + LLSD mResponse; + +public: + LLXMLRPCListener(const std::string& name, + bool i = false, + const LLSD& response = LLSD() + ) : + mName(name), + mImmediateResponse(i), + mResponse(response) + { + if(mResponse.isUndefined()) + { + mResponse["status"] = "Complete"; // StatusComplete + mResponse["errorcode"] = 0; + mResponse["error"] = "dummy response"; + mResponse["transfer_rate"] = 0; + mResponse["responses"]["login"] = true; + } + } + + void setResponse(const LLSD& r) + { + mResponse = r; + } + + bool handle_event(const LLSD& event) + { + std::cout << "LLXMLRPCListener called!: " << event << std::endl; + mEvent = event; + if(mImmediateResponse) + { + sendReply(); + } + return false; + } + + void sendReply() + { + LLEventPumps::instance().obtain(mEvent["reply"]).post(mResponse); + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1)); + } +}; + +namespace tut +{ + struct llviewerlogin_data + { + llviewerlogin_data() : + pumps(LLEventPumps::instance()) + {} + LLEventPumps& pumps; + }; + + typedef test_group llviewerlogin_group; + typedef llviewerlogin_group::object llviewerlogin_object; + llviewerlogin_group llviewerlogingrp("llviewerlogin"); + + template<> template<> + void llviewerlogin_object::test<1>() + { + // Testing login with immediate repsonses from Ares and XMLPRC + // The response from both requests will come before the post request exits. + // This tests an edge case of the login state handling. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + bool respond_immediately = true; + // Have 'dummy ares' repsond immediately. + LLAresListener dummyLLAres("dummy_llares", respond_immediately); + dummyLLAres.listenTo(llaresPump); + + // Have dummy XMLRPC respond immediately. + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "foo"; + credentials["last"] = "bar"; + credentials["passwd"] = "secret"; + + login.connect("login.bar.com", credentials); + + ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online"); + } + + template<> template<> + void llviewerlogin_object::test<2>() + { + // Tests a successful login in with delayed responses. + // Also includes 'failure' that cause the login module + // To re-attempt connection, once from a basic failure + // and once from the 'indeterminate' response. + + set_test_name("LLLogin multiple srv uris w/ success"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + bool respond_immediately = false; + bool multiple_addresses = true; + LLAresListener dummyLLAres("dummy_llares", respond_immediately, multiple_addresses); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "foo"; + credentials["last"] = "bar"; + credentials["passwd"] = "secret"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + // Test Authenticating State prior to first response. + ensure_equals("Auth state 1", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 1", listener.lastEvent()["data"]["attempt"].asInteger(), 1); + ensure_equals("URI 1", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.foo.com"); + + // First send emulated LLXMLRPCListener failure, + // this should return login to the authenticating step and increase the attempt + // count. + LLSD data; + data["status"] = "OtherError"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Fail back to authenticate 1", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 2", listener.lastEvent()["data"]["attempt"].asInteger(), 2); + ensure_equals("URI 2", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com"); + + // Now send the 'indeterminate' response. + data.clear(); + data["status"] = "Complete"; // StatusComplete + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "indeterminate"; + data["next_url"] = "login.indeterminate.com"; + data["next_method"] = "test_login_method"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Fail back to authenticate 2", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 3", listener.lastEvent()["data"]["attempt"].asInteger(), 3); + ensure_equals("URI 3", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.indeterminate.com"); + + // Finally let the auth succeed. + data.clear(); + data["status"] = "Complete"; // StatusComplete + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "true"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Success state", listener.lastEvent()["state"].asString(), "online"); + + login.disconnect(); + + ensure_equals("Disconnected state", listener.lastEvent()["state"].asString(), "offline"); + } + + template<> template<> + void llviewerlogin_object::test<3>() + { + // Test completed response, that fails to login. + set_test_name("LLLogin valid response, failure (eg. bad credentials)"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + LLAresListener dummyLLAres("dummy_llares"); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "who"; + credentials["last"] = "what"; + credentials["passwd"] = "badpasswd"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); + + // Send the failed auth request reponse + LLSD data; + data["status"] = "Complete"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "false"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); + } + + template<> template<> + void llviewerlogin_object::test<4>() + { + // Test incomplete response, that end the attempt. + set_test_name("LLLogin valid response, failure (eg. bad credentials)"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + LLAresListener dummyLLAres("dummy_llares"); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "these"; + credentials["last"] = "don't"; + credentials["passwd"] = "matter"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); + + // Send the failed auth request reponse + LLSD data; + data["status"] = "OtherError"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); + } +} diff --git a/install.xml b/install.xml index 24cbd37575..0183193734 100644 --- a/install.xml +++ b/install.xml @@ -207,30 +207,30 @@ darwin md5sum - 081ef195a856c708cc473c4421b4b290 + 95dda5da1fb66b690a03944fca1b2c53 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090223.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090427.tar.bz2 linux md5sum - b516a8576ecad0f957db7fc2cd972aac + 33e2d48a6c2207ade0f914fff99c18af url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090223a.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090427.tar.bz2 linux64 md5sum - 6db62bb7f141b3a1b3107e1f9aad0eb0 + cadb1934581b20f9b03aa18e2be7c55c url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090223a.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090427.tar.bz2 windows md5sum - 3b56fe9e8d2975c612639d0a5370ffe7 + c3ce8993eac0ca9546564d04131dc4f4 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090225.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090430.tar.bz2 @@ -1132,6 +1132,53 @@ anguage Infrstructure (CLI) international standard + pth + + copyright + Copyright (c) 1999-2006 Ralf S. Engelschall <rse@gnu.org> + description + Portable cooperative threads package, used to support Boost.Coroutine on Mac OS X 10.4 + license + lgpl + packages + + darwin + + md5sum + 533f4c710a209a6c4f205f81ccc0cfce + url + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-darwin-20090427.tar.bz2 + + linux + + md5sum + c5c2f73847c126e679d925beab48c7d4 + url + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux-20090427.tar.bz2 + + linux32 + + md5sum + c5c2f73847c126e679d925beab48c7d4 + url + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux32-20090427.tar.bz2 + + linux64 + + md5sum + c5c2f73847c126e679d925beab48c7d4 + url + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux64-20090427.tar.bz2 + + windows + + md5sum + c5c2f73847c126e679d925beab48c7d4 + url + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-windows-20090427.tar.bz2 + + + quicktime copyright -- cgit v1.3 From 01d390825a5d9ba37715b80cd0aa7aede022dcec Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Fri, 22 May 2009 23:27:16 +0000 Subject: DEV-27646 dll linkage for login module. Ok, finally got this to a point where it doesn't break the build and I can check in. llcommon can be built as a shared library (disabled but can be enabled with cmake cache var LLCOMMON_LINK_SHARED. reviewed by Mani on tuesday (I still need to get his suggested changes re-reviewed) --- indra/cmake/LLCommon.cmake | 5 ++ indra/llcommon/CMakeLists.txt | 10 ++- indra/llcommon/linden_common.h | 8 +- indra/llcommon/llapr.h | 22 +++--- indra/llcommon/llassettype.h | 2 +- indra/llcommon/llbase32.h | 4 +- indra/llcommon/llbase64.h | 4 +- indra/llcommon/llcommon.h | 2 +- indra/llcommon/llcrc.h | 2 +- indra/llcommon/llcriticaldamp.h | 2 +- indra/llcommon/llcursortypes.h | 2 +- indra/llcommon/lldate.h | 2 +- indra/llcommon/llerror.h | 8 +- indra/llcommon/llerrorcontrol.h | 48 ++++++------ indra/llcommon/llerrorthread.h | 2 +- indra/llcommon/llevent.h | 20 ++--- indra/llcommon/lleventcoro.h | 12 +-- indra/llcommon/lleventfilter.h | 6 +- indra/llcommon/llevents.h | 12 +-- indra/llcommon/llfasttimer.h | 4 +- indra/llcommon/llfile.h | 14 ++-- indra/llcommon/llfindlocale.h | 4 +- indra/llcommon/llfixedbuffer.h | 2 +- indra/llcommon/llformat.h | 2 +- indra/llcommon/llframetimer.h | 2 +- indra/llcommon/llheartbeat.h | 2 +- indra/llcommon/llliveappconfig.h | 2 +- indra/llcommon/lllivefile.h | 2 +- indra/llcommon/lllog.h | 4 +- indra/llcommon/llmd5.h | 2 +- indra/llcommon/llmemory.h | 2 +- indra/llcommon/llmemorystream.h | 4 +- indra/llcommon/llmetrics.h | 4 +- indra/llcommon/llmortician.h | 2 +- indra/llcommon/llpreprocessor.h | 41 +++++++--- indra/llcommon/llqueuedthread.h | 7 +- indra/llcommon/llrand.h | 12 +-- indra/llcommon/llrefcount.h | 4 +- indra/llcommon/llrun.h | 6 +- indra/llcommon/llsd.h | 4 +- indra/llcommon/llsdserialize.h | 18 ++--- indra/llcommon/llsdutil.h | 58 ++++----------- indra/llcommon/llsecondlifeurls.h | 30 ++++---- indra/llcommon/llsimplehash.h | 2 +- indra/llcommon/llstat.h | 16 ++-- indra/llcommon/llstreamtools.h | 36 ++++----- indra/llcommon/llstring.h | 87 ++++++++++++---------- indra/llcommon/llstringtable.h | 8 +- indra/llcommon/llsys.h | 18 ++--- indra/llcommon/llthread.h | 16 ++-- indra/llcommon/lltimer.h | 28 +++---- indra/llcommon/lluri.h | 8 +- indra/llcommon/lluuid.h | 8 +- indra/llcommon/llworkerthread.h | 4 +- indra/llcommon/metaclass.h | 8 +- indra/llcommon/metaproperty.h | 6 +- indra/llcommon/reflective.h | 4 +- indra/llcommon/tests/lleventcoro_test.cpp | 3 + indra/llcommon/timing.h | 3 +- indra/llcommon/u64.h | 10 +-- indra/llinventory/llparcel.cpp | 2 +- indra/llmath/CMakeLists.txt | 1 + indra/llmath/llsdutil_math.cpp | 2 +- indra/llmath/llsdutil_math.h | 70 +++++++++++++++++ indra/llmessage/llinstantmessage.cpp | 2 +- indra/llmessage/llpartdata.cpp | 2 + indra/llmessage/llregionpresenceverifier.cpp | 2 + indra/llmessage/llsdmessagebuilder.cpp | 1 + indra/llmessage/llsdmessagereader.cpp | 1 + indra/llprimitive/llprimitive.cpp | 2 +- indra/llprimitive/lltextureentry.cpp | 2 +- indra/llrender/llgl.cpp | 2 - indra/llui/llfunctorregistry.cpp | 1 + indra/lscript/lscript_execute/llscriptresource.cpp | 2 + indra/newview/llfeaturemanager.cpp | 6 +- indra/newview/llfloaterabout.cpp | 2 - indra/newview/llimview.cpp | 2 +- indra/newview/llpanelplace.cpp | 1 + indra/newview/llstartup.cpp | 2 +- indra/newview/llviewerparcelmgr.cpp | 1 + indra/newview/tests/llagentaccess_test.cpp | 2 + indra/test/llsdmessagebuilder_tut.cpp | 1 + indra/test/llsdmessagereader_tut.cpp | 1 + indra/test/llsdutil_tut.cpp | 1 + 84 files changed, 435 insertions(+), 346 deletions(-) create mode 100644 indra/llmath/llsdutil_math.h (limited to 'indra/llmessage') diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index 410766e4f9..e9e3784e69 100644 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -13,3 +13,8 @@ set(LLCOMMON_INCLUDE_DIRS ) set(LLCOMMON_LIBRARIES llcommon) + +set(LLCOMMON_LINK_SHARED OFF CACHE BOOL "Build the llcommon target as a shared library.") +if(LLCOMMON_LINK_SHARED) + add_definitions(-DLL_COMMON_LINK_SHARED=1) +endif(LLCOMMON_LINK_SHARED) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 62476fd59d..71ec6cb8e4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -5,6 +5,7 @@ project(llcommon) include(00-Common) include(LLAddBuildTest) include(LLCommon) +include(Linking) include(Boost) include_directories( @@ -215,13 +216,20 @@ set_source_files_properties(${llcommon_HEADER_FILES} list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) -add_library (llcommon ${llcommon_SOURCE_FILES}) +if(LLCOMMON_LINK_SHARED) + add_library (llcommon SHARED ${llcommon_SOURCE_FILES}) + add_definitions(-DLL_COMMON_BUILD=1) +else(LLCOMMON_LINK_SHARED) + add_library (llcommon ${llcommon_SOURCE_FILES}) +endif(LLCOMMON_LINK_SHARED) + target_link_libraries( llcommon ${APRUTIL_LIBRARIES} ${APR_LIBRARIES} ${EXPAT_LIBRARIES} ${ZLIB_LIBRARIES} + ${WINDOWS_LIBRARIES} ${BOOST_PROGRAM_OPTIONS_LIBRARY} ${BOOST_REGEX_LIBRARY} ) diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 9adf24a492..d0ab5e969f 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -72,13 +72,7 @@ #ifdef LL_WINDOWS // Reenable warnings we disabled above #pragma warning (3 : 4702) // unreachable code, we like level 3, not 4 -// level 4 warnings that we need to disable: -#pragma warning (disable : 4100) // unreferenced formal parameter -#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) -#pragma warning (disable : 4244) // possible loss of data on conversions -#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template -#pragma warning (disable : 4512) // assignment operator could not be generated -#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) +// moved msvc warnings to llpreprocessor.h *TODO - delete this comment after merge conflicts are unlikely -brad #endif // LL_WINDOWS // Linden only libs in alpha-order other than stdtypes.h diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 44ad2dd50f..5bd4b8a0f0 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -48,24 +48,24 @@ #include "apr_atomic.h" #include "llstring.h" -extern apr_thread_mutex_t* gLogMutexp; +extern LL_COMMON_API apr_thread_mutex_t* gLogMutexp; /** * @brief initialize the common apr constructs -- apr itself, the * global pool, and a mutex. */ -void ll_init_apr(); +void LL_COMMON_API ll_init_apr(); /** * @brief Cleanup those common apr constructs. */ -void ll_cleanup_apr(); +void LL_COMMON_API ll_cleanup_apr(); // //LL apr_pool //manage apr_pool_t, destroy allocated apr_pool in the destruction function. // -class LLAPRPool +class LL_COMMON_API LLAPRPool { public: LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE) ; @@ -91,7 +91,7 @@ protected: //which clears memory automatically. //so it can not hold static data or data after memory is cleared // -class LLVolatileAPRPool : public LLAPRPool +class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool { public: LLVolatileAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE); @@ -117,7 +117,7 @@ private: * destructor handles the unlock. Instances of this class are * not thread safe. */ -class LLScopedLock : private boost::noncopyable +class LL_COMMON_API LLScopedLock : private boost::noncopyable { public: /** @@ -148,7 +148,7 @@ protected: apr_thread_mutex_t* mMutex; }; -template class LLAtomic32 +template class LL_COMMON_API LLAtomic32 { public: LLAtomic32() {}; @@ -191,7 +191,7 @@ typedef LLAtomic32 LLAtomicS32; // 1, a temperary pool passed to an APRFile function, which is used within this function and only once. // 2, a global pool. // -class LLAPRFile +class LL_COMMON_API LLAPRFile { private: apr_file_t* mFile ; @@ -249,10 +249,10 @@ public: * APR_SUCCESS. * @return Returns true if status is an error condition. */ -bool ll_apr_warn_status(apr_status_t status); +bool LL_COMMON_API ll_apr_warn_status(apr_status_t status); -void ll_apr_assert_status(apr_status_t status); +void LL_COMMON_API ll_apr_assert_status(apr_status_t status); -extern "C" apr_pool_t* gAPRPoolp; // Global APR memory pool +extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool #endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 0ee4ae2821..f9df6ddd92 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -37,7 +37,7 @@ #include "stdenums.h" // for EDragAndDropType -class LLAssetType +class LL_COMMON_API LLAssetType { public: enum EType diff --git a/indra/llcommon/llbase32.h b/indra/llcommon/llbase32.h index 63a93e11ab..0697f7b8e2 100644 --- a/indra/llcommon/llbase32.h +++ b/indra/llcommon/llbase32.h @@ -32,9 +32,9 @@ */ #ifndef LLBASE32_H -#define LLBASE32_h +#define LLBASE32_H -class LLBase32 +class LL_COMMON_API LLBase32 { public: static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index 58414bba8b..c48fea2478 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -32,9 +32,9 @@ */ #ifndef LLBASE64_H -#define LLBASE64_h +#define LLBASE64_H -class LLBase64 +class LL_COMMON_API LLBase64 { public: static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index a1808e8a6c..b36471f9f8 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -37,7 +37,7 @@ #include "lltimer.h" #include "llfile.h" -class LLCommon +class LL_COMMON_API LLCommon { public: static void initClass(); diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index 27fae7d269..74369062cc 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -50,7 +50,7 @@ // llinfos << "File crc: " << crc.getCRC() << llendl; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLCRC +class LL_COMMON_API LLCRC { protected: U32 mCurrent; diff --git a/indra/llcommon/llcriticaldamp.h b/indra/llcommon/llcriticaldamp.h index ad98284a6c..1ea5914b5b 100644 --- a/indra/llcommon/llcriticaldamp.h +++ b/indra/llcommon/llcriticaldamp.h @@ -38,7 +38,7 @@ #include "llframetimer.h" -class LLCriticalDamp +class LL_COMMON_API LLCriticalDamp { public: LLCriticalDamp(); diff --git a/indra/llcommon/llcursortypes.h b/indra/llcommon/llcursortypes.h index bea70351b7..836ecc3c04 100644 --- a/indra/llcommon/llcursortypes.h +++ b/indra/llcommon/llcursortypes.h @@ -77,6 +77,6 @@ enum ECursorType { UI_CURSOR_COUNT // Number of elements in this enum (NOT a cursor) }; -ECursorType getCursorFromString(const std::string& cursor_string); +LL_COMMON_API ECursorType getCursorFromString(const std::string& cursor_string); #endif // LL_LLCURSORTYPES_H diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index 29a9030b6d..c096d7ddd5 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -46,7 +46,7 @@ * * The date class represents a point in time after epoch - 1970-01-01. */ -class LLDate +class LL_COMMON_API LLDate { public: /** diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 6794be4904..6ccdf2174b 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -129,9 +129,9 @@ namespace LLError They are not intended for general use. */ - class CallSite; + class LL_COMMON_API CallSite; - class Log + class LL_COMMON_API Log { public: static bool shouldLog(CallSite&); @@ -140,7 +140,7 @@ namespace LLError static void flush(std::ostringstream*, const CallSite&); }; - class CallSite + class LL_COMMON_API CallSite { // Represents a specific place in the code where a message is logged // This is public because it is used by the macros below. It is not @@ -189,7 +189,7 @@ namespace LLError //LLCallStacks is designed not to be thread-safe. //so try not to use it in multiple parallel threads at same time. //Used in a single thread at a time is fine. - class LLCallStacks + class LL_COMMON_API LLCallStacks { private: static char** sBuffer ; diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index c9424f8a5e..1a559ed7e0 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -52,12 +52,12 @@ class LLSD; namespace LLError { - void initForServer(const std::string& identity); + LL_COMMON_API void initForServer(const std::string& identity); // resets all logging settings to defaults needed by server processes // logs to stderr, syslog, and windows debug log // the identity string is used for in the syslog - void initForApplication(const std::string& dir); + LL_COMMON_API void initForApplication(const std::string& dir); // resets all logging settings to defaults needed by applicaitons // logs to stderr and windows debug log // sets up log configuration from the file logcontrol.xml in dir @@ -68,13 +68,13 @@ namespace LLError Setting a level means log messages at that level or above. */ - void setPrintLocation(bool); - void setDefaultLevel(LLError::ELevel); - void setFunctionLevel(const std::string& function_name, LLError::ELevel); - void setClassLevel(const std::string& class_name, LLError::ELevel); - void setFileLevel(const std::string& file_name, LLError::ELevel); + LL_COMMON_API void setPrintLocation(bool); + LL_COMMON_API void setDefaultLevel(LLError::ELevel); + LL_COMMON_API void setFunctionLevel(const std::string& function_name, LLError::ELevel); + LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel); + LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel); - void configure(const LLSD&); + LL_COMMON_API void configure(const LLSD&); // the LLSD can configure all of the settings // usually read automatically from the live errorlog.xml file @@ -84,21 +84,21 @@ namespace LLError */ typedef boost::function FatalFunction; - void crashAndLoop(const std::string& message); + LL_COMMON_API void crashAndLoop(const std::string& message); // Default fatal function: access null pointer and loops forever - void setFatalFunction(const FatalFunction&); + LL_COMMON_API void setFatalFunction(const FatalFunction&); // The fatal function will be called when an message of LEVEL_ERROR // is logged. Note: supressing a LEVEL_ERROR message from being logged // (by, for example, setting a class level to LEVEL_NONE), will keep // the that message from causing the fatal funciton to be invoked. - FatalFunction getFatalFunction(); + LL_COMMON_API FatalFunction getFatalFunction(); // Retrieve the previously-set FatalFunction /// temporarily override the FatalFunction for the duration of a /// particular scope, e.g. for unit tests - class OverrideFatalFunction + class LL_COMMON_API OverrideFatalFunction { public: OverrideFatalFunction(const FatalFunction& func): @@ -116,15 +116,15 @@ namespace LLError }; typedef std::string (*TimeFunction)(); - std::string utcTime(); + LL_COMMON_API std::string utcTime(); - void setTimeFunction(TimeFunction); + LL_COMMON_API void setTimeFunction(TimeFunction); // The function is use to return the current time, formatted for // display by those error recorders that want the time included. - class Recorder + class LL_COMMON_API Recorder { // An object that handles the actual output or error messages. public: @@ -138,17 +138,17 @@ namespace LLError // included in the text of the message }; - void addRecorder(Recorder*); - void removeRecorder(Recorder*); + LL_COMMON_API void addRecorder(Recorder*); + LL_COMMON_API void removeRecorder(Recorder*); // each error message is passed to each recorder via recordMessage() - void logToFile(const std::string& filename); - void logToFixedBuffer(LLFixedBuffer*); + LL_COMMON_API void logToFile(const std::string& filename); + LL_COMMON_API void logToFixedBuffer(LLFixedBuffer*); // Utilities to add recorders for logging to a file or a fixed buffer // A second call to the same function will remove the logger added // with the first. // Passing the empty string or NULL to just removes any prior. - std::string logFileName(); + LL_COMMON_API std::string logFileName(); // returns name of current logging file, empty string if none @@ -157,11 +157,11 @@ namespace LLError */ class Settings; - Settings* saveAndResetSettings(); - void restoreSettings(Settings *); + LL_COMMON_API Settings* saveAndResetSettings(); + LL_COMMON_API void restoreSettings(Settings *); - std::string abbreviateFile(const std::string& filePath); - int shouldLogCallCount(); + LL_COMMON_API std::string abbreviateFile(const std::string& filePath); + LL_COMMON_API int shouldLogCallCount(); }; diff --git a/indra/llcommon/llerrorthread.h b/indra/llcommon/llerrorthread.h index f1d6ffc34f..3121d29675 100644 --- a/indra/llcommon/llerrorthread.h +++ b/indra/llcommon/llerrorthread.h @@ -35,7 +35,7 @@ #include "llthread.h" -class LLErrorThread : public LLThread +class LL_COMMON_API LLErrorThread : public LLThread { public: LLErrorThread(); diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 2cc8577219..192cb84fea 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -41,13 +41,13 @@ namespace LLOldEvents { -class LLEventListener; -class LLEvent; -class LLEventDispatcher; -class LLObservable; +class LL_COMMON_API LLEventListener; +class LL_COMMON_API LLEvent; +class LL_COMMON_API LLEventDispatcher; +class LL_COMMON_API LLObservable; // Abstract event. All events derive from LLEvent -class LLEvent : public LLThreadSafeRefCount +class LL_COMMON_API LLEvent : public LLThreadSafeRefCount { protected: virtual ~LLEvent(); @@ -75,7 +75,7 @@ private: }; // Abstract listener. All listeners derive from LLEventListener -class LLEventListener : public LLThreadSafeRefCount +class LL_COMMON_API LLEventListener : public LLThreadSafeRefCount { protected: virtual ~LLEventListener(); @@ -92,7 +92,7 @@ public: }; // A listener which tracks references to it and cleans up when it's deallocated -class LLSimpleListener : public LLEventListener +class LL_COMMON_API LLSimpleListener : public LLEventListener { public: void clearDispatchers(); @@ -104,7 +104,7 @@ protected: std::vector mDispatchers; }; -class LLObservable; // defined below +class LL_COMMON_API LLObservable; // defined below // A structure which stores a Listener and its metadata struct LLListenerEntry @@ -117,7 +117,7 @@ struct LLListenerEntry // Base class for a dispatcher - an object which listens // to events being fired and relays them to their // appropriate destinations. -class LLEventDispatcher : public LLThreadSafeRefCount +class LL_COMMON_API LLEventDispatcher : public LLThreadSafeRefCount { protected: virtual ~LLEventDispatcher(); @@ -160,7 +160,7 @@ private: // In order for this class to work properly, it needs // an instance of an LLEventDispatcher to route events to their // listeners. -class LLObservable +class LL_COMMON_API LLObservable { public: // Initialize with the default Dispatcher diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h index 7232d1780f..5726ea0f65 100644 --- a/indra/llcommon/lleventcoro.h +++ b/indra/llcommon/lleventcoro.h @@ -106,7 +106,7 @@ namespace LLEventDetail * that's okay, since it won't collide with any listener name used by the * earlier coroutine since that earlier coroutine no longer exists. */ - std::string listenerNameForCoro(const void* self); + LL_COMMON_API std::string listenerNameForCoro(const void* self); /** * Implement behavior described for postAndWait()'s @a replyPumpNamePath @@ -126,7 +126,7 @@ namespace LLEventDetail * In the degenerate case in which @a path is an empty array, @a dest will * @em become @a value rather than @em containing it. */ - void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); + LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); } // namespace LLEventDetail /** @@ -378,7 +378,7 @@ LLSD errorException(const LLEventWithID& result, const std::string& desc); * because it's not an error in event processing: rather, this exception * announces an event that bears error information (for some other API). */ -class LLErrorEvent: public std::runtime_error +class LL_COMMON_API LLErrorEvent: public std::runtime_error { public: LLErrorEvent(const std::string& what, const LLSD& data): @@ -397,7 +397,7 @@ private: * Like errorException(), save that this trips a fatal error using LL_ERRS * rather than throwing an exception. */ -LLSD errorLog(const LLEventWithID& result, const std::string& desc); +LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc); /** * Certain event APIs require the name of an LLEventPump on which they should @@ -413,7 +413,7 @@ LLSD errorLog(const LLEventWithID& result, const std::string& desc); * 4. Call your LLEventTempStream's wait() method to wait for the reply. * 5. Let the LLCoroEventPump go out of scope. */ -class LLCoroEventPump +class LL_COMMON_API LLCoroEventPump { public: LLCoroEventPump(const std::string& name="coro"): @@ -458,7 +458,7 @@ private: * success response, the other for error response. Extend LLCoroEventPump * for the two-pump use case. */ -class LLCoroEventPumps +class LL_COMMON_API LLCoroEventPumps { public: LLCoroEventPumps(const std::string& name="coro", diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index fe1a631c6b..89f0c7ea43 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -20,7 +20,7 @@ /** * Generic base class */ -class LLEventFilter: public LLEventStream +class LL_COMMON_API LLEventFilter: public LLEventStream { public: /// construct a standalone LLEventFilter @@ -60,7 +60,7 @@ private: * @NOTE This is an abstract base class so that, for testing, we can use an * alternate "timer" that doesn't actually consume real time. */ -class LLEventTimeoutBase: public LLEventFilter +class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter { public: /// construct standalone @@ -169,7 +169,7 @@ private: }; /// Production implementation of LLEventTimoutBase -class LLEventTimeout: public LLEventTimeoutBase +class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase { public: LLEventTimeout(); diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 20061f09c6..e84d9a50ee 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -125,7 +125,7 @@ typedef boost::signals2::scoped_connection LLTempBoundListener; * LLListenerOrPumpName::Empty. Test for this condition beforehand using * either if (param) or if (! param). */ -class LLListenerOrPumpName +class LL_COMMON_API LLListenerOrPumpName { public: /// passing string name of LLEventPump @@ -172,13 +172,13 @@ private: /***************************************************************************** * LLEventPumps *****************************************************************************/ -class LLEventPump; +class LL_COMMON_API LLEventPump; /** * LLEventPumps is a Singleton manager through which one typically accesses * this subsystem. */ -class LLEventPumps: public LLSingleton +class LL_COMMON_API LLEventPumps: public LLSingleton { friend class LLSingleton; public: @@ -307,7 +307,7 @@ typedef boost::signals2::trackable LLEventTrackable; * destruction. Please see LLEventTrackable documentation for situations in * which this may be perilous across threads. */ -class LLEventPump: public LLEventTrackable +class LL_COMMON_API LLEventPump: public LLEventTrackable { public: /** @@ -528,7 +528,7 @@ protected: * LLEventStream is a thin wrapper around LLStandardSignal. Posting an * event immediately calls all registered listeners. */ -class LLEventStream: public LLEventPump +class LL_COMMON_API LLEventStream: public LLEventPump { public: LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -545,7 +545,7 @@ public: * LLEventQueue isa LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ -class LLEventQueue: public LLEventPump +class LL_COMMON_API LLEventQueue: public LLEventPump { public: LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 94b51119e4..f2dae09fdf 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -35,9 +35,9 @@ #define FAST_TIMER_ON 1 -U64 get_cpu_clock_count(); +U64 LL_COMMON_API get_cpu_clock_count(); -class LLFastTimer +class LL_COMMON_API LLFastTimer { public: enum EFastTimerType diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index c6092f7b9c..fea5d3ed2b 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -70,7 +70,7 @@ typedef struct stat llstat; #include "llstring.h" // safe char* -> std::string conversion -class LLFile +class LL_COMMON_API LLFile { public: // All these functions take UTF8 path/filenames. @@ -95,7 +95,7 @@ public: #if USE_LLFILESTREAMS -class llifstream : public std::basic_istream < char , std::char_traits < char > > +class LL_COMMON_API llifstream : public std::basic_istream < char , std::char_traits < char > > { // input stream associated with a C stream public: @@ -136,7 +136,7 @@ private: }; -class llofstream : public std::basic_ostream< char , std::char_traits < char > > +class LL_COMMON_API llofstream : public std::basic_ostream< char , std::char_traits < char > > { public: typedef std::basic_ostream< char , std::char_traits < char > > _Myt; @@ -185,7 +185,7 @@ private: //#define llifstream std::ifstream //#define llofstream std::ofstream -class llifstream : public std::ifstream +class LL_COMMON_API llifstream : public std::ifstream { public: llifstream() : std::ifstream() @@ -203,7 +203,7 @@ public: }; -class llofstream : public std::ofstream +class LL_COMMON_API llofstream : public std::ofstream { public: llofstream() : std::ofstream() @@ -231,7 +231,7 @@ public: * and should only be used for config files and the like -- not in a * loop. */ -std::streamsize llifstream_size(llifstream& fstr); -std::streamsize llofstream_size(llofstream& fstr); +std::streamsize LL_COMMON_API llifstream_size(llifstream& fstr); +std::streamsize LL_COMMON_API llofstream_size(llofstream& fstr); #endif // not LL_LLFILE_H diff --git a/indra/llcommon/llfindlocale.h b/indra/llcommon/llfindlocale.h index f17c7740f3..b812a065db 100644 --- a/indra/llcommon/llfindlocale.h +++ b/indra/llcommon/llfindlocale.h @@ -59,8 +59,8 @@ typedef enum { /* This allocates/fills in a FL_Locale structure with pointers to strings (which should be treated as static), or NULL for inappropriate / undetected fields. */ -FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain); +LL_COMMON_API FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain); /* This should be used to free the struct written by FL_FindLocale */ -void FL_FreeLocale(FL_Locale **locale); +LL_COMMON_API void FL_FreeLocale(FL_Locale **locale); #endif /*__findlocale_h_*/ diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h index 992a024df1..51d0701736 100644 --- a/indra/llcommon/llfixedbuffer.h +++ b/indra/llcommon/llfixedbuffer.h @@ -41,7 +41,7 @@ // Fixed size buffer for console output and other things. -class LLFixedBuffer +class LL_COMMON_API LLFixedBuffer { public: LLFixedBuffer(const U32 max_lines = 20); diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h index 44c62d9710..dc64edb26d 100644 --- a/indra/llcommon/llformat.h +++ b/indra/llcommon/llformat.h @@ -40,6 +40,6 @@ // *NOTE: buffer limited to 1024, (but vsnprintf prevents overrun) // should perhaps be replaced with boost::format. -std::string llformat(const char *fmt, ...); +std::string LL_COMMON_API llformat(const char *fmt, ...); #endif // LL_LLFORMAT_H diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 8f51272af2..be2d9b0703 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -43,7 +43,7 @@ #include "lltimer.h" #include "timing.h" -class LLFrameTimer +class LL_COMMON_API LLFrameTimer { public: LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} diff --git a/indra/llcommon/llheartbeat.h b/indra/llcommon/llheartbeat.h index fecb5b1e54..6f7026970f 100644 --- a/indra/llcommon/llheartbeat.h +++ b/indra/llcommon/llheartbeat.h @@ -40,7 +40,7 @@ // Note: Win32 does not support the heartbeat/smackdown system; // heartbeat-delivery turns into a no-op there. -class LLHeartbeat +class LL_COMMON_API LLHeartbeat { public: // secs_between_heartbeat: after a heartbeat is successfully delivered, diff --git a/indra/llcommon/llliveappconfig.h b/indra/llcommon/llliveappconfig.h index 55d84a4778..3251a7c50e 100644 --- a/indra/llcommon/llliveappconfig.h +++ b/indra/llcommon/llliveappconfig.h @@ -37,7 +37,7 @@ class LLApp; -class LLLiveAppConfig : public LLLiveFile +class LL_COMMON_API LLLiveAppConfig : public LLLiveFile { public: // To use this, instantiate a LLLiveAppConfig object inside your main loop. diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h index a3a9cf49ab..a6f9996767 100644 --- a/indra/llcommon/lllivefile.h +++ b/indra/llcommon/lllivefile.h @@ -36,7 +36,7 @@ const F32 configFileRefreshRate = 5.0; // seconds -class LLLiveFile +class LL_COMMON_API LLLiveFile { public: LLLiveFile(const std::string &filename, const F32 refresh_period = 5.f); diff --git a/indra/llcommon/lllog.h b/indra/llcommon/lllog.h index 7ac6c8aa42..b0ec570c01 100644 --- a/indra/llcommon/lllog.h +++ b/indra/llcommon/lllog.h @@ -39,9 +39,9 @@ class LLLogImpl; class LLApp; -class LLSD; +class LL_COMMON_API LLSD; -class LLLog +class LL_COMMON_API LLLog { public: LLLog(LLApp* app); diff --git a/indra/llcommon/llmd5.h b/indra/llcommon/llmd5.h index d8bca03e4e..df9d7324ab 100644 --- a/indra/llcommon/llmd5.h +++ b/indra/llcommon/llmd5.h @@ -80,7 +80,7 @@ const int MD5RAW_BYTES = 16; const int MD5HEX_STR_SIZE = 33; // char hex[MD5HEX_STR_SIZE]; with null const int MD5HEX_STR_BYTES = 32; // message system fixed size -class LLMD5 { +class LL_COMMON_API LLMD5 { // first, some types: typedef unsigned int uint4; // assumes integer is 4 words long typedef unsigned short int uint2; // assumes short integer is 2 words long diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index a72e58034b..2c356db965 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -38,7 +38,7 @@ extern S32 gTotalDAlloc; extern S32 gTotalDAUse; extern S32 gDACount; -class LLMemory +class LL_COMMON_API LLMemory { public: static void initClass(); diff --git a/indra/llcommon/llmemorystream.h b/indra/llcommon/llmemorystream.h index f3486324c5..fa0f5d22f2 100644 --- a/indra/llcommon/llmemorystream.h +++ b/indra/llcommon/llmemorystream.h @@ -52,7 +52,7 @@ * be careful to always pass in a valid memory location that exists * for at least as long as this streambuf. */ -class LLMemoryStreamBuf : public std::streambuf +class LL_COMMON_API LLMemoryStreamBuf : public std::streambuf { public: LLMemoryStreamBuf(const U8* start, S32 length); @@ -74,7 +74,7 @@ protected: * be careful to always pass in a valid memory location that exists * for at least as long as this streambuf. */ -class LLMemoryStream : public std::istream +class LL_COMMON_API LLMemoryStream : public std::istream { public: LLMemoryStream(const U8* start, S32 length); diff --git a/indra/llcommon/llmetrics.h b/indra/llcommon/llmetrics.h index 1d91e8c8a2..11e10a5a2e 100644 --- a/indra/llcommon/llmetrics.h +++ b/indra/llcommon/llmetrics.h @@ -36,9 +36,9 @@ #define LL_LLMETRICS_H class LLMetricsImpl; -class LLSD; +class LL_COMMON_API LLSD; -class LLMetrics +class LL_COMMON_API LLMetrics { public: LLMetrics(); diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index fcda3df58e..27bd8cd9b5 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -35,7 +35,7 @@ #include "stdtypes.h" -class LLMortician +class LL_COMMON_API LLMortician { public: LLMortician() { mIsDead = FALSE; } diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 2e4fd4787a..5ff7814997 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -93,17 +93,6 @@ #endif -// Deal with the differeneces on Windows -#if LL_MSVC -namespace snprintf_hack -{ - int snprintf(char *str, size_t size, const char *format, ...); -} - -// #define snprintf safe_snprintf /* Flawfinder: ignore */ -using snprintf_hack::snprintf; -#endif // LL_MSVC - // Static linking with apr on windows needs to be declared. #ifdef LL_WINDOWS #ifndef APR_DECLARE_STATIC @@ -133,6 +122,36 @@ using snprintf_hack::snprintf; #pragma warning( disable : 4503 ) // 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation. #pragma warning( disable : 4800 ) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) #pragma warning( disable : 4996 ) // warning: deprecated + +// level 4 warnings that we need to disable: +#pragma warning (disable : 4100) // unreferenced formal parameter +#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) +#pragma warning (disable : 4244) // possible loss of data on conversions +#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template +#pragma warning (disable : 4512) // assignment operator could not be generated +#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) + +#pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class +#pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class #endif // LL_MSVC +#if LL_WINDOWS +#define LL_DLLEXPORT __declspec(dllexport) +#define LL_DLLIMPORT __declspec(dllimport) +#else +#define LL_DLLEXPORT +#define LL_DLLIMPORT +#endif // LL_WINDOWS + + +#if LL_COMMON_LINK_SHARED +# if LL_COMMON_BUILD +# define LL_COMMON_API LL_DLLEXPORT +# else //LL_COMMON_BUILD +# define LL_COMMON_API LL_DLLIMPORT +# endif //LL_COMMON_BUILD +#else // LL_COMMON_LINK_SHARED +# define LL_COMMON_API +#endif // LL_COMMON_LINK_SHARED + #endif // not LL_LINDEN_PREPROCESSOR_H diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 3ba43e1e07..b3cde22b40 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -47,7 +47,7 @@ // Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small // It is assumed that LLQueuedThreads are rarely created/destroyed. -class LLQueuedThread : public LLThread +class LL_COMMON_API LLQueuedThread : public LLThread { //------------------------------------------------------------------------ public: @@ -80,7 +80,7 @@ public: //------------------------------------------------------------------------ public: - class QueuedRequest : public LLSimpleHashEntry + class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry { friend class LLQueuedThread; @@ -148,6 +148,9 @@ protected: } }; + template class LL_COMMON_API std::set; + + //------------------------------------------------------------------------ public: diff --git a/indra/llcommon/llrand.h b/indra/llcommon/llrand.h index d12597bb53..30fec9b982 100644 --- a/indra/llcommon/llrand.h +++ b/indra/llcommon/llrand.h @@ -65,32 +65,32 @@ /** *@brief Generate a float from [0, RAND_MAX). */ -S32 ll_rand(); +S32 LL_COMMON_API ll_rand(); /** *@brief Generate a float from [0, val) or (val, 0]. */ -S32 ll_rand(S32 val); +S32 LL_COMMON_API ll_rand(S32 val); /** *@brief Generate a float from [0, 1.0). */ -F32 ll_frand(); +F32 LL_COMMON_API ll_frand(); /** *@brief Generate a float from [0, val) or (val, 0]. */ -F32 ll_frand(F32 val); +F32 LL_COMMON_API ll_frand(F32 val); /** *@brief Generate a double from [0, 1.0). */ -F64 ll_drand(); +F64 LL_COMMON_API ll_drand(); /** *@brief Generate a double from [0, val) or (val, 0]. */ -F64 ll_drand(F64 val); +F64 LL_COMMON_API ll_drand(F64 val); /** * @brief typedefs for good boost lagged fibonacci. diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index 540a18b8a0..5f102509fd 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -37,9 +37,9 @@ // see llthread.h for LLThreadSafeRefCount //---------------------------------------------------------------------------- -class LLRefCount +class LL_COMMON_API LLRefCount { -protected: +private: LLRefCount(const LLRefCount&); // not implemented private: LLRefCount&operator=(const LLRefCount&); // not implemented diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index 77b23d9051..afe65fd734 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -38,7 +38,7 @@ #include #include -class LLRunnable; +class LL_COMMON_API LLRunnable; /** * @class LLRunner @@ -48,7 +48,7 @@ class LLRunnable; * which are scheduled to run on a repeating or one time basis. * @see LLRunnable */ -class LLRunner +class LL_COMMON_API LLRunner { public: /** @@ -149,7 +149,7 @@ protected: * something useful. * @see LLRunner */ -class LLRunnable +class LL_COMMON_API LLRunnable { public: LLRunnable(); diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index d2845a3757..552bb57498 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -89,7 +89,7 @@ @nosubgrouping */ -class LLSD +class LL_COMMON_API LLSD { public: LLSD(); ///< initially Undefined @@ -387,7 +387,7 @@ struct llsd_select_string : public std::unary_function } }; -std::ostream& operator<<(std::ostream& s, const LLSD& llsd); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLSD& llsd); /** QUESTIONS & TO DOS - Would Binary be more convenient as usigned char* buffer semantics? diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h index 7463d1e5dd..4b32f0afcd 100644 --- a/indra/llcommon/llsdserialize.h +++ b/indra/llcommon/llsdserialize.h @@ -44,7 +44,7 @@ * @class LLSDParser * @brief Abstract base class for LLSD parsers. */ -class LLSDParser : public LLRefCount +class LL_COMMON_API LLSDParser : public LLRefCount { protected: /** @@ -221,7 +221,7 @@ protected: * @class LLSDNotationParser * @brief Parser which handles the original notation format for LLSD. */ -class LLSDNotationParser : public LLSDParser +class LL_COMMON_API LLSDNotationParser : public LLSDParser { protected: /** @@ -294,7 +294,7 @@ private: * @class LLSDXMLParser * @brief Parser which handles XML format LLSD. */ -class LLSDXMLParser : public LLSDParser +class LL_COMMON_API LLSDXMLParser : public LLSDParser { protected: /** @@ -342,7 +342,7 @@ private: * @class LLSDBinaryParser * @brief Parser which handles binary formatted LLSD. */ -class LLSDBinaryParser : public LLSDParser +class LL_COMMON_API LLSDBinaryParser : public LLSDParser { protected: /** @@ -407,7 +407,7 @@ private: * @class LLSDFormatter * @brief Abstract base class for formatting LLSD. */ -class LLSDFormatter : public LLRefCount +class LL_COMMON_API LLSDFormatter : public LLRefCount { protected: /** @@ -479,7 +479,7 @@ protected: * @class LLSDNotationFormatter * @brief Formatter which outputs the original notation format for LLSD. */ -class LLSDNotationFormatter : public LLSDFormatter +class LL_COMMON_API LLSDNotationFormatter : public LLSDFormatter { protected: /** @@ -520,7 +520,7 @@ public: * @class LLSDXMLFormatter * @brief Formatter which outputs the LLSD as XML. */ -class LLSDXMLFormatter : public LLSDFormatter +class LL_COMMON_API LLSDXMLFormatter : public LLSDFormatter { protected: /** @@ -588,7 +588,7 @@ protected: * Map: '{' + 4 byte integer size every(key + value) + '}'
* map keys are serialized as 'k' + 4 byte integer size + string */ -class LLSDBinaryFormatter : public LLSDFormatter +class LL_COMMON_API LLSDBinaryFormatter : public LLSDFormatter { protected: /** @@ -677,7 +677,7 @@ typedef LLSDOStreamer LLSDXMLStreamer; * @class LLSDSerialize * @brief Serializer / deserializer for the various LLSD formats */ -class LLSDSerialize +class LL_COMMON_API LLSDSerialize { public: enum ELLSD_Serialize diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 0752f8aff1..a4175be450 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -35,62 +35,32 @@ #ifndef LL_LLSDUTIL_H #define LL_LLSDUTIL_H -#include "llsd.h" - -// vector3 -class LLVector3; -LLSD ll_sd_from_vector3(const LLVector3& vec); -LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector4 -class LLVector4; -LLSD ll_sd_from_vector4(const LLVector4& vec); -LLVector4 ll_vector4_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector3d (double) -class LLVector3d; -LLSD ll_sd_from_vector3d(const LLVector3d& vec); -LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector2 -class LLVector2; -LLSD ll_sd_from_vector2(const LLVector2& vec); -LLVector2 ll_vector2_from_sd(const LLSD& sd); - -// Quaternion -class LLQuaternion; -LLSD ll_sd_from_quaternion(const LLQuaternion& quat); -LLQuaternion ll_quaternion_from_sd(const LLSD& sd); - -// color4 -class LLColor4; -LLSD ll_sd_from_color4(const LLColor4& c); -LLColor4 ll_color4_from_sd(const LLSD& sd); +class LL_COMMON_API LLSD; // U32 -LLSD ll_sd_from_U32(const U32); -U32 ll_U32_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U32(const U32); +LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); // U64 -LLSD ll_sd_from_U64(const U64); -U64 ll_U64_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U64(const U64); +LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); // IP Address -LLSD ll_sd_from_ipaddr(const U32); -U32 ll_ipaddr_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); +LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); // Binary to string -LLSD ll_string_from_binary(const LLSD& sd); +LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); //String to binary -LLSD ll_binary_from_string(const LLSD& sd); +LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); // Serializes sd to static buffer and returns pointer, useful for gdb debugging. -char* ll_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_print_sd(const LLSD& sd); // Serializes sd to static buffer and returns pointer, using "pretty printing" mode. -char* ll_pretty_print_sd_ptr(const LLSD* sd); -char* ll_pretty_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); +LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); //compares the structure of an LLSD to a template LLSD and stores the //"valid" values in a 3rd LLSD. Default values @@ -99,7 +69,7 @@ char* ll_pretty_print_sd(const LLSD& sd); //Returns false if the test is of same type but values differ in type //Otherwise, returns true -BOOL compare_llsd_with_template( +LL_COMMON_API BOOL compare_llsd_with_template( const LLSD& llsd_to_test, const LLSD& template_llsd, LLSD& resultant_llsd); @@ -157,7 +127,7 @@ BOOL compare_llsd_with_template( * meaningfully converted to the requested type. The same goes for UUID, Date * and URI. */ -std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); +LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); // Simple function to copy data out of input & output iterators if // there is no need for casting. diff --git a/indra/llcommon/llsecondlifeurls.h b/indra/llcommon/llsecondlifeurls.h index a2e5f0b9c6..bd2f9f7604 100644 --- a/indra/llcommon/llsecondlifeurls.h +++ b/indra/llcommon/llsecondlifeurls.h @@ -34,49 +34,49 @@ #define LL_LLSECONDLIFEURLS_H /* // Account registration web page -extern const std::string CREATE_ACCOUNT_URL; +LL_COMMON_API extern const std::string CREATE_ACCOUNT_URL; // Manage Account -extern const std::string MANAGE_ACCOUNT; +LL_COMMON_API extern const std::string MANAGE_ACCOUNT; -extern const std::string AUCTION_URL; +LL_COMMON_API extern const std::string AUCTION_URL; -extern const std::string EVENTS_URL; +LL_COMMON_API extern const std::string EVENTS_URL; */ // Tier up to a new land level. -extern const std::string TIER_UP_URL; +LL_COMMON_API extern const std::string TIER_UP_URL; // Tier up to a new land level. -extern const std::string LAND_URL; +LL_COMMON_API extern const std::string LAND_URL; // How to get DirectX 9 -extern const std::string DIRECTX_9_URL; +LL_COMMON_API extern const std::string DIRECTX_9_URL; /* // Upgrade from basic membership to premium membership -extern const std::string UPGRADE_TO_PREMIUM_URL; +LL_COMMON_API extern const std::string UPGRADE_TO_PREMIUM_URL; // Out of date VIA chipset -extern const std::string VIA_URL; +LL_COMMON_API extern const std::string VIA_URL; // Support URL -extern const std::string SUPPORT_URL; +LL_COMMON_API extern const std::string SUPPORT_URL; // Linden Blogs page -extern const std::string BLOGS_URL; +LL_COMMON_API extern const std::string BLOGS_URL; // Currency page -extern const std::string BUY_CURRENCY_URL; +LL_COMMON_API extern const std::string BUY_CURRENCY_URL; // LSL script wiki -extern const std::string LSL_DOC_URL; +LL_COMMON_API extern const std::string LSL_DOC_URL; // SL KnowledgeBase page -extern const std::string SL_KB_URL; +LL_COMMON_API extern const std::string SL_KB_URL; // Release Notes Redirect URL for Server and Viewer -extern const std::string RELEASE_NOTES_BASE_URL; +LL_COMMON_API extern const std::string RELEASE_NOTES_BASE_URL; */ #endif diff --git a/indra/llcommon/llsimplehash.h b/indra/llcommon/llsimplehash.h index 0ba2a3014c..5df93b646e 100644 --- a/indra/llcommon/llsimplehash.h +++ b/indra/llcommon/llsimplehash.h @@ -64,7 +64,7 @@ public: }; template -class LLSimpleHash +class LL_COMMON_API LLSimpleHash { public: LLSimpleHash() diff --git a/indra/llcommon/llstat.h b/indra/llcommon/llstat.h index bad18f46a0..5d77215beb 100644 --- a/indra/llcommon/llstat.h +++ b/indra/llcommon/llstat.h @@ -40,7 +40,7 @@ #include "llframetimer.h" #include "llfile.h" -class LLSD; +class LL_COMMON_API LLSD; // Set this if longer stats are needed #define ENABLE_LONG_TIME_STATS 0 @@ -52,7 +52,7 @@ class LLSD; // amounts of time with very low memory cost. // -class LLStatAccum +class LL_COMMON_API LLStatAccum { protected: LLStatAccum(bool use_frame_timer); @@ -116,7 +116,7 @@ public: F64 mLastSampleValue; }; -class LLStatMeasure : public LLStatAccum +class LL_COMMON_API LLStatMeasure : public LLStatAccum // gathers statistics about things that are measured // ex.: tempature, time dilation { @@ -131,7 +131,7 @@ public: }; -class LLStatRate : public LLStatAccum +class LL_COMMON_API LLStatRate : public LLStatAccum // gathers statistics about things that can be counted over time // ex.: LSL instructions executed, messages sent, simulator frames completed // renders it in terms of rate of thing per second @@ -147,7 +147,7 @@ public: }; -class LLStatTime : public LLStatAccum +class LL_COMMON_API LLStatTime : public LLStatAccum // gathers statistics about time spent in a block of code // measure average duration per second in the block { @@ -178,7 +178,7 @@ private: // Use this class on the stack to record statistics about an area of code -class LLPerfBlock +class LL_COMMON_API LLPerfBlock { public: struct StatEntry @@ -220,7 +220,7 @@ private: // ---------------------------------------------------------------------------- -class LLPerfStats +class LL_COMMON_API LLPerfStats { public: LLPerfStats(const std::string& process_name = "unknown", S32 process_pid = 0); @@ -256,7 +256,7 @@ private: }; // ---------------------------------------------------------------------------- -class LLStat +class LL_COMMON_API LLStat { private: typedef std::multimap stat_map_t; diff --git a/indra/llcommon/llstreamtools.h b/indra/llcommon/llstreamtools.h index a6dc4d51e2..f64e761409 100644 --- a/indra/llcommon/llstreamtools.h +++ b/indra/llcommon/llstreamtools.h @@ -39,23 +39,23 @@ // unless specifed otherwise these all return input_stream.good() // skips spaces and tabs -bool skip_whitespace(std::istream& input_stream); +LL_COMMON_API bool skip_whitespace(std::istream& input_stream); // skips whitespace and newlines -bool skip_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_emptyspace(std::istream& input_stream); // skips emptyspace and lines that start with a # -bool skip_comments_and_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_comments_and_emptyspace(std::istream& input_stream); // skips to character after next newline -bool skip_line(std::istream& input_stream); +LL_COMMON_API bool skip_line(std::istream& input_stream); // skips to beginning of next non-emptyspace -bool skip_to_next_word(std::istream& input_stream); +LL_COMMON_API bool skip_to_next_word(std::istream& input_stream); // skips to character after the end of next keyword // a 'keyword' is defined as the first word on a line -bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); +LL_COMMON_API bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); // skip_to_start_of_next_keyword() is disabled -- might tickle corruption bug // in windows iostream @@ -65,14 +65,14 @@ bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream // characters are pulled out of input_stream and appended to output_string // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream); -bool get_line(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream); // characters are pulled out of input_stream (up to a max of 'n') // and appended to output_string // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream, int n); -bool get_line(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream, int n); // unget_line() is disabled -- might tickle corruption bug in windows iostream //// backs up the input_stream by line_size + 1 characters @@ -82,28 +82,28 @@ bool get_line(std::string& output_string, std::istream& input_stream, int n); // removes the last char in 'line' if it matches 'c' // returns true if removed last char -bool remove_last_char(char c, std::string& line); +LL_COMMON_API bool remove_last_char(char c, std::string& line); // replaces escaped characters with the correct characters from left to right // "\\" ---> '\\' // "\n" ---> '\n' -void unescape_string(std::string& line); +LL_COMMON_API void unescape_string(std::string& line); // replaces unescaped characters with expanded equivalents from left to right // '\\' ---> "\\" // '\n' ---> "\n" -void escape_string(std::string& line); +LL_COMMON_API void escape_string(std::string& line); // replaces each '\n' character with ' ' -void replace_newlines_with_whitespace(std::string& line); +LL_COMMON_API void replace_newlines_with_whitespace(std::string& line); // erases any double-quote characters in line -void remove_double_quotes(std::string& line); +LL_COMMON_API void remove_double_quotes(std::string& line); // the 'keyword' is defined as the first word on a line // the 'value' is everything after the keyword on the same line // starting at the first non-whitespace and ending right before the newline -void get_keyword_and_value(std::string& keyword, +LL_COMMON_API void get_keyword_and_value(std::string& keyword, std::string& value, const std::string& line); @@ -111,13 +111,13 @@ void get_keyword_and_value(std::string& keyword, // read anymore or until we hit the count. Some istream // implimentations have a max that they will read. // Returns the number of bytes read. -std::streamsize fullread( +LL_COMMON_API std::streamsize fullread( std::istream& istr, char* buf, std::streamsize requested); -std::istream& operator>>(std::istream& str, const char *tocheck); +LL_COMMON_API std::istream& operator>>(std::istream& str, const char *tocheck); #endif diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 1aba001353..da1900eadf 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -147,7 +147,7 @@ struct char_traits }; #endif -class LLStringOps +class LL_COMMON_API LLStringOps { private: static long sltOffset; @@ -194,13 +194,13 @@ public: * @brief Return a string constructed from in without crashing if the * pointer is NULL. */ -std::string ll_safe_string(const char* in); -std::string ll_safe_string(const char* in, S32 maxlen); +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); // Allowing assignments from non-strings into format_map_t is apparently // *really* error-prone, so subclass std::string with just basic c'tors. -class LLFormatMapString +class LL_COMMON_API LLFormatMapString { public: LLFormatMapString() {}; @@ -375,7 +375,7 @@ inline std::string chop_tail_copy( * @brief This translates a nybble stored as a hex value from 0-f back * to a nybble in the low order bits of the return byte. */ -U8 hex_as_nybble(char hex); +LL_COMMON_API U8 hex_as_nybble(char hex); /** * @brief read the contents of a file into a string. @@ -386,8 +386,8 @@ U8 hex_as_nybble(char hex); * @param filename The full name of the file to read. * @return Returns true on success. If false, str is unmodified. */ -bool _read_file_into_string(std::string& str, const std::string& filename); -bool iswindividual(llwchar elem); +LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem); /** * Unicode support @@ -396,52 +396,52 @@ bool iswindividual(llwchar elem); // Make the incoming string a utf8 string. Replaces any unknown glyph // with the UNKOWN_CHARACTER. Once any unknown glph is found, the rest // of the data may not be recovered. -std::string rawstr_to_utf8(const std::string& raw); +LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); // // We should never use UTF16 except when communicating with Win32! // typedef std::basic_string llutf16string; -LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); -LLWString utf16str_to_wstring(const llutf16string &utf16str); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); -llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); -llutf16string wstring_to_utf16str(const LLWString &utf32str); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); -llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); -llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); -LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); -LLWString utf8str_to_wstring(const std::string &utf8str); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str); // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } // -S32 wchar_to_utf8chars(llwchar inchar, char* outchars); +LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); -std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); -std::string wstring_to_utf8str(const LLWString &utf32str); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); -std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); -std::string utf16str_to_utf8str(const llutf16string &utf16str); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); // Length of this UTF32 string in bytes when transformed to UTF8 -S32 wstring_utf8_length(const LLWString& wstr); +LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); // Length in bytes of this wide char in a UTF8 string -S32 wchar_utf8_length(const llwchar wc); +LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); -std::string utf8str_tolower(const std::string& utf8str); +LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); +LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); // Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); +LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); // Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); /** * @brief Properly truncate a utf8 string to a maximum byte count. @@ -453,11 +453,11 @@ S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset * @param max_len The maximum number of bytes in the return value. * @return Returns a valid utf8 string with byte count <= max_len. */ -std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); +LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); -std::string utf8str_trim(const std::string& utf8str); +LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); -S32 utf8str_compare_insensitive( +LL_COMMON_API S32 utf8str_compare_insensitive( const std::string& lhs, const std::string& rhs); @@ -468,17 +468,17 @@ S32 utf8str_compare_insensitive( * @param target_char The wchar to be replaced * @param replace_char The wchar which is written on replace */ -std::string utf8str_substChar( +LL_COMMON_API std::string utf8str_substChar( const std::string& utf8str, const llwchar target_char, const llwchar replace_char); -std::string utf8str_makeASCII(const std::string& utf8str); +LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); // Hack - used for evil notecards. -std::string mbcsstring_makeASCII(const std::string& str); +LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); -std::string utf8str_removeCRLF(const std::string& utf8str); +LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); #if LL_WINDOWS @@ -503,14 +503,21 @@ std::string utf8str_removeCRLF(const std::string& utf8str); * formatted string. * */ -int safe_snprintf(char* str, size_t size, const char* format, ...); + +// Deal with the differeneces on Windows +namespace snprintf_hack +{ + LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...); +} + +using snprintf_hack::snprintf; /** * @brief Convert a wide string to std::string * * This replaces the unsafe W2A macro from ATL. */ -std::string ll_convert_wide_to_string(const wchar_t* in); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); //@} #endif // LL_WINDOWS @@ -533,7 +540,7 @@ namespace LLStringFn * with zero non-printable characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_nonprintable_in_ascii( + LL_COMMON_API void replace_nonprintable_in_ascii( std::basic_string& string, char replacement); @@ -547,7 +554,7 @@ namespace LLStringFn * with zero non-printable characters and zero pipe characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, + LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string& str, char replacement); @@ -556,7 +563,7 @@ namespace LLStringFn * Returns a copy of the string with those characters removed. * Works with US ASCII and UTF-8 encoded strings. JC */ - std::string strip_invalid_xml(const std::string& input); + LL_COMMON_API std::string strip_invalid_xml(const std::string& input); /** @@ -567,7 +574,7 @@ namespace LLStringFn * with zero non-printable characters. * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. */ - void replace_ascii_controlchars( + LL_COMMON_API void replace_ascii_controlchars( std::basic_string& string, char replacement); } diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index 4492063275..b13b016396 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -56,7 +56,7 @@ const U32 MAX_STRINGS_LENGTH = 256; -class LLStringTableEntry +class LL_COMMON_API LLStringTableEntry { public: LLStringTableEntry(const char *str) @@ -81,7 +81,7 @@ public: S32 mCount; }; -class LLStringTable +class LL_COMMON_API LLStringTable { public: LLStringTable(int tablesize); @@ -115,7 +115,7 @@ public: #endif }; -extern LLStringTable gStringTable; +extern LL_COMMON_API LLStringTable gStringTable; //============================================================================ @@ -125,7 +125,7 @@ extern LLStringTable gStringTable; typedef const std::string* LLStdStringHandle; -class LLStdStringTable +class LL_COMMON_API LLStdStringTable { public: LLStdStringTable(S32 tablesize = 0) diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 03f48ca018..c2c45bec9a 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -45,7 +45,7 @@ #include #include -class LLOSInfo +class LL_COMMON_API LLOSInfo { public: LLOSInfo(); @@ -70,7 +70,7 @@ private: }; -class LLCPUInfo +class LL_COMMON_API LLCPUInfo { public: LLCPUInfo(); @@ -99,7 +99,7 @@ private: // // CLASS LLMemoryInfo -class LLMemoryInfo +class LL_COMMON_API LLMemoryInfo /*! @brief Class to query the memory subsystem @@ -123,15 +123,15 @@ public: }; -std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); -std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); -std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); // gunzip srcfile into dstfile. Returns FALSE on error. -BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); // gzip srcfile into dstfile. Returns FALSE on error. -BOOL gzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); -extern LLCPUInfo gSysCPU; +extern LL_COMMON_API LLCPUInfo gSysCPU; #endif // LL_LLSYS_H diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index f25339f48d..e6bf95aaa9 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -38,11 +38,11 @@ #include "apr_thread_cond.h" -class LLThread; -class LLMutex; -class LLCondition; +class LL_COMMON_API LLThread; +class LL_COMMON_API LLMutex; +class LL_COMMON_API LLCondition; -class LLThread +class LL_COMMON_API LLThread { public: typedef enum e_thread_status @@ -130,7 +130,7 @@ protected: //============================================================================ -class LLMutex +class LL_COMMON_API LLMutex { public: LLMutex(apr_pool_t *apr_poolp); // NULL pool constructs a new pool for the mutex @@ -147,7 +147,7 @@ protected: }; // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). -class LLCondition : public LLMutex +class LL_COMMON_API LLCondition : public LLMutex { public: LLCondition(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well. @@ -194,7 +194,7 @@ void LLThread::unlockData() // see llmemory.h for LLPointer<> definition -class LLThreadSafeRefCount +class LL_COMMON_API LLThreadSafeRefCount { public: static void initThreadSafeRefCount(); // creates sMutex @@ -246,7 +246,7 @@ private: // Simple responder for self destructing callbacks // Pure virtual class -class LLResponder : public LLThreadSafeRefCount +class LL_COMMON_API LLResponder : public LLThreadSafeRefCount { protected: virtual ~LLResponder(); diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 0319bec45b..d009c0f5f7 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -55,7 +55,7 @@ const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; -class LLTimer +class LL_COMMON_API LLTimer { public: static LLTimer *sTimer; // global timer @@ -114,17 +114,17 @@ public: // // Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. // -U64 get_clock_count(); -F64 calc_clock_frequency(U32 msecs); -void update_clock_frequencies(); +LL_COMMON_API U64 get_clock_count(); +LL_COMMON_API F64 calc_clock_frequency(U32 msecs); +LL_COMMON_API void update_clock_frequencies(); // Sleep for milliseconds -void ms_sleep(U32 ms); -U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); +LL_COMMON_API void ms_sleep(U32 ms); +LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); // Returns the correct UTC time in seconds, like time(NULL). // Useful on the viewer, which may have its local clock set wrong. -time_t time_corrected(); +LL_COMMON_API time_t time_corrected(); static inline time_t time_min() { @@ -155,24 +155,24 @@ static inline time_t time_max() } // Correction factor used by time_corrected() above. -extern S32 gUTCOffset; +extern LL_COMMON_API S32 gUTCOffset; // Is the current computer (in its current time zone) // observing daylight savings time? -BOOL is_daylight_savings(); +LL_COMMON_API BOOL is_daylight_savings(); // Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time // Usage: // S32 utc_time; // utc_time = time_corrected(); // struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); -void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); -void secondsToTimecodeString(F32 current_time, std::string& tcstring); +LL_COMMON_API void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); +LL_COMMON_API void secondsToTimecodeString(F32 current_time, std::string& tcstring); // class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LLEventTimer : protected LLInstanceTracker +class LL_COMMON_API LLEventTimer : protected LLInstanceTracker { public: LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds @@ -190,4 +190,6 @@ protected: F32 mPeriod; }; +U64 LL_COMMON_API totalTime(); // Returns current system time in microseconds + #endif diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 156d80b97e..a35598ffe5 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -37,9 +37,9 @@ #include -class LLSD; -class LLUUID; -class LLApp; +class LL_COMMON_API LLSD; +class LL_COMMON_API LLUUID; +class LL_COMMON_API LLApp; /** * @@ -47,7 +47,7 @@ class LLApp; * See: http://www.ietf.org/rfc/rfc3986.txt * */ -class LLURI +class LL_COMMON_API LLURI { public: LLURI(); diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 4b32138a06..68e403fd4f 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -47,7 +47,7 @@ struct uuid_time_t { U32 low; }; -class LLUUID +class LL_COMMON_API LLUUID { public: // @@ -106,8 +106,8 @@ public: LLUUID combine(const LLUUID& other) const; void combine(const LLUUID& other, LLUUID& result) const; - friend std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); - friend std::istream& operator>>(std::istream& s, LLUUID &uuid); + friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); + friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) void toString(std::string& out) const; @@ -323,7 +323,7 @@ typedef std::set uuid_list_t; */ typedef LLUUID LLAssetID; -class LLTransactionID : public LLUUID +class LL_COMMON_API LLTransactionID : public LLUUID { public: LLTransactionID() : LLUUID() { } diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 19407f4463..a12bd52a64 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -50,7 +50,7 @@ class LLWorkerClass; // Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small // It is assumed that LLWorkerThreads are rarely created/destroyed. -class LLWorkerThread : public LLQueuedThread +class LL_COMMON_API LLWorkerThread : public LLQueuedThread { public: class WorkRequest : public LLQueuedThread::QueuedRequest @@ -113,7 +113,7 @@ public: // Only one background task can be active at a time (per instance). // i.e. don't call addWork() if haveWork() returns true -class LLWorkerClass +class LL_COMMON_API LLWorkerClass { friend class LLWorkerThread; friend class LLWorkerThread::WorkRequest; diff --git a/indra/llcommon/metaclass.h b/indra/llcommon/metaclass.h index cc10f1675f..8b93e0d6d5 100644 --- a/indra/llcommon/metaclass.h +++ b/indra/llcommon/metaclass.h @@ -40,10 +40,10 @@ #include "stdtypes.h" -class LLReflective; -class LLMetaProperty; -class LLMetaMethod; -class LLMetaClass +class LL_COMMON_API LLReflective; +class LL_COMMON_API LLMetaProperty; +class LL_COMMON_API LLMetaMethod; +class LL_COMMON_API LLMetaClass { public: diff --git a/indra/llcommon/metaproperty.h b/indra/llcommon/metaproperty.h index e5ac35907c..96e1b314a4 100644 --- a/indra/llcommon/metaproperty.h +++ b/indra/llcommon/metaproperty.h @@ -39,9 +39,9 @@ #include "llsd.h" #include "reflective.h" -class LLMetaClass; -class LLReflective; -class LLMetaProperty +class LL_COMMON_API LLMetaClass; +class LL_COMMON_API LLReflective; +class LL_COMMON_API LLMetaProperty { public: LLMetaProperty(const std::string& name, const LLMetaClass& object_class); diff --git a/indra/llcommon/reflective.h b/indra/llcommon/reflective.h index e2c18ebc6d..541712538b 100644 --- a/indra/llcommon/reflective.h +++ b/indra/llcommon/reflective.h @@ -35,8 +35,8 @@ #ifndef LL_REFLECTIVE_H #define LL_REFLECTIVE_H -class LLMetaClass; -class LLReflective +class LL_COMMON_API LLMetaClass; +class LL_COMMON_API LLReflective { public: LLReflective(); diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index cd39ac4df3..695b1ca9f4 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -53,6 +53,9 @@ #include #include #include + +#include "linden_common.h" + #include #include diff --git a/indra/llcommon/timing.h b/indra/llcommon/timing.h index 2b9f60adad..140ce1fcaa 100644 --- a/indra/llcommon/timing.h +++ b/indra/llcommon/timing.h @@ -43,7 +43,6 @@ const F32 SEC_TO_MICROSEC = 1000000.f; const U64 SEC_TO_MICROSEC_U64 = 1000000; const U32 SEC_PER_DAY = 86400; -// This is just a stub, implementation in lltimer.cpp. This file will be deprecated in the future. -U64 totalTime(); // Returns current system time in microseconds +// functionality has been moved lltimer.{cpp,h}. This file will be deprecated in the future. #endif diff --git a/indra/llcommon/u64.h b/indra/llcommon/u64.h index 09a6b3e18d..eb51131e94 100644 --- a/indra/llcommon/u64.h +++ b/indra/llcommon/u64.h @@ -39,14 +39,14 @@ * @param str The string to parse. * @return Returns the first U64 value found in the string or 0 on failure. */ -U64 str_to_U64(const std::string& str); +LL_COMMON_API U64 str_to_U64(const std::string& str); /** * @brief Given a U64 value, return a printable representation. * @param value The U64 to turn into a printable character array. * @return Returns the result string. */ -std::string U64_to_str(U64 value); +LL_COMMON_API std::string U64_to_str(U64 value); /** * @brief Given a U64 value, return a printable representation. @@ -65,16 +65,16 @@ std::string U64_to_str(U64 value); * @param result_size The size of the buffer allocated. Use U64_BUF. * @return Returns the result pointer. */ -char* U64_to_str(U64 value, char* result, S32 result_size); +LL_COMMON_API char* U64_to_str(U64 value, char* result, S32 result_size); /** * @brief Convert a U64 to the closest F64 value. */ -F64 U64_to_F64(const U64 value); +LL_COMMON_API F64 U64_to_F64(const U64 value); /** * @brief Helper function to wrap strtoull() which is not available on windows. */ -U64 llstrtou64(const char* str, char** end, S32 base); +LL_COMMON_API U64 llstrtou64(const char* str, char** end, S32 base); #endif diff --git a/indra/llinventory/llparcel.cpp b/indra/llinventory/llparcel.cpp index 60ebc44a3e..bd8c0d58e5 100644 --- a/indra/llinventory/llparcel.cpp +++ b/indra/llinventory/llparcel.cpp @@ -43,7 +43,7 @@ #include "llsdutil.h" #include "lltransactiontypes.h" #include "lltransactionflags.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "message.h" #include "u64.h" diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index 6a329fabb6..f84cdf1563 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -60,6 +60,7 @@ set(llmath_HEADER_FILES llv4vector3.h llvolume.h llvolumemgr.h + llsdutil_math.h m3math.h m4math.h raytrace.h diff --git a/indra/llmath/llsdutil_math.cpp b/indra/llmath/llsdutil_math.cpp index c5176681ce..1bd12ae513 100644 --- a/indra/llmath/llsdutil_math.cpp +++ b/indra/llmath/llsdutil_math.cpp @@ -34,7 +34,7 @@ #include "linden_common.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "v3math.h" #include "v4math.h" diff --git a/indra/llmath/llsdutil_math.h b/indra/llmath/llsdutil_math.h new file mode 100644 index 0000000000..121f4b746a --- /dev/null +++ b/indra/llmath/llsdutil_math.h @@ -0,0 +1,70 @@ +/** + * @file llsdutil_math.h + * @author Brad + * @date 2009-05-19 + * @brief Utility classes, functions, etc, for using structured data with math classes. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLSDUTIL_MATH_H +#define LL_LLSDUTIL_MATH_H + +class LL_COMMON_API LLSD; + +// vector3 +class LLVector3; +LLSD ll_sd_from_vector3(const LLVector3& vec); +LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index = 0); + +// vector4 +class LLVector4; +LLSD ll_sd_from_vector4(const LLVector4& vec); +LLVector4 ll_vector4_from_sd(const LLSD& sd, S32 start_index = 0); + +// vector3d (double) +class LLVector3d; +LLSD ll_sd_from_vector3d(const LLVector3d& vec); +LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index = 0); + +// vector2 +class LLVector2; +LLSD ll_sd_from_vector2(const LLVector2& vec); +LLVector2 ll_vector2_from_sd(const LLSD& sd); + +// Quaternion +class LLQuaternion; +LLSD ll_sd_from_quaternion(const LLQuaternion& quat); +LLQuaternion ll_quaternion_from_sd(const LLSD& sd); + +// color4 +class LLColor4; +LLSD ll_sd_from_color4(const LLColor4& c); +LLColor4 ll_color4_from_sd(const LLSD& sd); + +#endif // LL_LLSDUTIL_MATH_H diff --git a/indra/llmessage/llinstantmessage.cpp b/indra/llmessage/llinstantmessage.cpp index aa64232e42..e41b332e7b 100644 --- a/indra/llmessage/llinstantmessage.cpp +++ b/indra/llmessage/llinstantmessage.cpp @@ -40,7 +40,7 @@ #include "lluuid.h" #include "llsd.h" #include "llsdserialize.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "llpointer.h" #include "message.h" diff --git a/indra/llmessage/llpartdata.cpp b/indra/llmessage/llpartdata.cpp index 485bc6aa44..9376cde7b5 100644 --- a/indra/llmessage/llpartdata.cpp +++ b/indra/llmessage/llpartdata.cpp @@ -39,6 +39,8 @@ #include "v4coloru.h" #include "llsdutil.h" +#include "llsdutil_math.h" + const S32 PS_PART_DATA_BLOCK_SIZE = 4 + 2 + 4 + 4 + 2 + 2; // 18 diff --git a/indra/llmessage/llregionpresenceverifier.cpp b/indra/llmessage/llregionpresenceverifier.cpp index 0527d5cb8d..e6be4af07b 100644 --- a/indra/llmessage/llregionpresenceverifier.cpp +++ b/indra/llmessage/llregionpresenceverifier.cpp @@ -30,6 +30,8 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "llregionpresenceverifier.h" #include "llhttpclientinterface.h" #include diff --git a/indra/llmessage/llsdmessagebuilder.cpp b/indra/llmessage/llsdmessagebuilder.cpp index 21937f022f..6e41b03895 100755 --- a/indra/llmessage/llsdmessagebuilder.cpp +++ b/indra/llmessage/llsdmessagebuilder.cpp @@ -37,6 +37,7 @@ #include "llmessagetemplate.h" #include "llquaternion.h" #include "llsdutil.h" +#include "llsdutil_math.h" #include "llsdserialize.h" #include "u64.h" #include "v3dmath.h" diff --git a/indra/llmessage/llsdmessagereader.cpp b/indra/llmessage/llsdmessagereader.cpp index e699ec9e28..845a12d23b 100755 --- a/indra/llmessage/llsdmessagereader.cpp +++ b/indra/llmessage/llsdmessagereader.cpp @@ -38,6 +38,7 @@ #include "llsdmessagebuilder.h" #include "llsdutil.h" +#include "llsdutil_math.h" #include "v3math.h" #include "v4math.h" #include "v3dmath.h" diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index 13facc0d58..a0fcecfe5f 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -43,7 +43,7 @@ #include "llvolumemgr.h" #include "llstring.h" #include "lldatapacker.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "llprimtexturelist.h" /** diff --git a/indra/llprimitive/lltextureentry.cpp b/indra/llprimitive/lltextureentry.cpp index 3bcd831142..2736d54cc8 100644 --- a/indra/llprimitive/lltextureentry.cpp +++ b/indra/llprimitive/lltextureentry.cpp @@ -33,7 +33,7 @@ #include "linden_common.h" #include "lltextureentry.h" -#include "llsdutil.h" +#include "llsdutil_math.h" const U8 DEFAULT_BUMP_CODE = 0; // no bump or shininess diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 61194c4ecf..f39e88f483 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -547,8 +547,6 @@ void LLGLManager::shutdownGL() // these are used to turn software blending on. They appear in the Debug/Avatar menu // presence of vertex skinning/blending or vertex programs will set these to FALSE by default. -extern LLCPUInfo gSysCPU; - void LLGLManager::initExtensions() { #if LL_MESA_HEADLESS diff --git a/indra/llui/llfunctorregistry.cpp b/indra/llui/llfunctorregistry.cpp index 0c5b1655b1..5f9644f258 100644 --- a/indra/llui/llfunctorregistry.cpp +++ b/indra/llui/llfunctorregistry.cpp @@ -31,6 +31,7 @@ * $/LicenseInfo$ **/ +#include "linden_common.h" #include "llfunctorregistry.h" // This is a default functor always resident in the system. diff --git a/indra/lscript/lscript_execute/llscriptresource.cpp b/indra/lscript/lscript_execute/llscriptresource.cpp index 6c4776c2e4..cd3696ab3f 100644 --- a/indra/lscript/lscript_execute/llscriptresource.cpp +++ b/indra/lscript/lscript_execute/llscriptresource.cpp @@ -30,6 +30,8 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "llscriptresource.h" #include "llerror.h" diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index 35613b7c34..e863be40e1 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -44,6 +44,7 @@ #include "llgl.h" #include "llsecondlifeurls.h" +#include "llappviewer.h" #include "llviewercontrol.h" #include "llworld.h" #include "lldrawpoolterrain.h" @@ -58,11 +59,6 @@ #include "lldxhardware.h" #endif -// -// externs -// -extern LLMemoryInfo gSysMemory; -extern LLCPUInfo gSysCPU; #if LL_DARWIN const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt"; diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index 9b5d322ba6..356ecff236 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -61,8 +61,6 @@ #include "llmediamanager.h" -extern LLCPUInfo gSysCPU; -extern LLMemoryInfo gSysMemory; extern U32 gPacketsIn; static std::string get_viewer_release_notes_url(); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 26ce5c5ac4..9e3bc76c9c 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -40,7 +40,7 @@ #include "llerror.h" #include "llbutton.h" #include "llhttpclient.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "llstring.h" #include "lluictrlfactory.h" diff --git a/indra/newview/llpanelplace.cpp b/indra/newview/llpanelplace.cpp index 227faaa584..ee194ee7e8 100644 --- a/indra/newview/llpanelplace.cpp +++ b/indra/newview/llpanelplace.cpp @@ -59,6 +59,7 @@ //#include "llviewermenu.h" // create_landmark() #include "llweb.h" #include "llsdutil.h" +#include "llsdutil_math.h" //static std::list LLPanelPlace::sAllPanels; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index eddc23b0c4..41568217c0 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -70,7 +70,7 @@ #include "llregionhandle.h" #include "llsd.h" #include "llsdserialize.h" -#include "llsdutil.h" +#include "llsdutil_math.h" #include "llsecondlifeurls.h" #include "llstring.h" #include "lluserrelations.h" diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index da9587a359..224ccf41b6 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -58,6 +58,7 @@ #include "llparcelselection.h" #include "llresmgr.h" #include "llsdutil.h" +#include "llsdutil_math.h" #include "llstatusbar.h" #include "llui.h" #include "llviewerimage.h" diff --git a/indra/newview/tests/llagentaccess_test.cpp b/indra/newview/tests/llagentaccess_test.cpp index 42872d85fb..e08193f785 100644 --- a/indra/newview/tests/llagentaccess_test.cpp +++ b/indra/newview/tests/llagentaccess_test.cpp @@ -29,6 +29,8 @@ * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ + +#include "linden_common.h" #include "../test/lltut.h" #include "../llagentaccess.h" diff --git a/indra/test/llsdmessagebuilder_tut.cpp b/indra/test/llsdmessagebuilder_tut.cpp index 27ab127772..9edb915703 100755 --- a/indra/test/llsdmessagebuilder_tut.cpp +++ b/indra/test/llsdmessagebuilder_tut.cpp @@ -44,6 +44,7 @@ #include "v3dmath.h" #include "v3math.h" #include "v4math.h" +#include "llsdutil.cpp" #include "llsdutil_math.cpp" #include "lltemplatemessagebuilder.h" diff --git a/indra/test/llsdmessagereader_tut.cpp b/indra/test/llsdmessagereader_tut.cpp index 36cfe5ebfc..f11e148cca 100755 --- a/indra/test/llsdmessagereader_tut.cpp +++ b/indra/test/llsdmessagereader_tut.cpp @@ -42,6 +42,7 @@ #include "message.h" #include "llsdmessagereader.h" #include "llsdutil.h" +#include "llsdutil_math.h" namespace tut { diff --git a/indra/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp index 093a29652c..35ab80e791 100644 --- a/indra/test/llsdutil_tut.cpp +++ b/indra/test/llsdutil_tut.cpp @@ -44,6 +44,7 @@ #include "v4math.h" #include "llquaternion.h" #include "llsdutil.h" +#include "llsdutil_math.h" #include #include -- cgit v1.3 From f910157c1662dedb9791efc1439ff09f1f3efbf8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 27 May 2009 21:17:22 +0000 Subject: DEV-31979: Introduce LLReqID, a class to help individual event API listeners implement the ["reqid"] convention. This convention dictates that a response LLSD from each such API should contain a ["reqid"] key whose value echoes the ["reqid"] value, if any, in the request LLSD. Add LLReqID support to LLAresListener's "rewriteURI" service, LLSDMessage, LLCapabilityListener and LLXMLRPCListener. --- indra/llcommon/llevents.cpp | 25 +++++++++ indra/llcommon/llevents.h | 81 +++++++++++++++++++++++++++ indra/llmessage/llareslistener.cpp | 39 ++++++------- indra/llmessage/llsdmessage.cpp | 7 ++- indra/llmessage/llsdmessage.h | 3 + indra/llmessage/tests/llareslistener_test.cpp | 12 +++- indra/newview/llcapabilitylistener.cpp | 1 + indra/newview/llxmlrpclistener.cpp | 4 +- 8 files changed, 147 insertions(+), 25 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 7e3c6964dc..c2fa79a524 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -39,6 +39,8 @@ #endif // other Linden headers #include "stringize.h" +#include "llerror.h" +#include "llsdutil.h" /***************************************************************************** * queue_names: specify LLEventPump names that should be instantiated as @@ -506,3 +508,26 @@ bool LLListenerOrPumpName::operator()(const LLSD& event) const } return (*mListener)(event); } + +void LLReqID::stamp(LLSD& response) const +{ + if (! (response.isUndefined() || response.isMap())) + { + // If 'response' was previously completely empty, it's okay to + // turn it into a map. If it was already a map, then it should be + // okay to add a key. But if it was anything else (e.g. a scalar), + // assigning a ["reqid"] key will DISCARD the previous value, + // replacing it with a map. That would be Bad. + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") leaving non-map response unmodified: " + << response << LL_ENDL; + return; + } + LLSD oldReqid(response["reqid"]); + if (! (oldReqid.isUndefined() || llsd_equals(oldReqid, mReqid))) + { + LL_INFOS("LLReqID") << "stamp(" << mReqid << ") preserving existing [\"reqid\"] value " + << oldReqid << " in response: " << response << LL_ENDL; + return; + } + response["reqid"] = mReqid; +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index e84d9a50ee..73a35af035 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -563,6 +563,87 @@ private: EventQueue mEventQueue; }; +/***************************************************************************** +* LLReqID +*****************************************************************************/ +/** + * This class helps the implementer of a given event API to honor the + * ["reqid"] convention. By this convention, each event API stamps into its + * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if + * any, from the corresponding request. + * + * This supports an (atypical, but occasionally necessary) use case in which + * two or more asynchronous requests are multiplexed onto the same ["reply"] + * LLEventPump. Since the response events could arrive in arbitrary order, the + * caller must be able to demux them. It does so by matching the ["reqid"] + * value in each response with the ["reqid"] value in the corresponding + * request. + * + * It is the caller's responsibility to ensure distinct ["reqid"] values for + * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: + * the "namespace" of unique ["reqid"] values is simply the set of requests + * specifying the same ["reply"] LLEventPump name. + * + * Making a given event API echo the request's ["reqid"] into the response is + * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a + * place to put these comments. We hope that each time a coder implements a + * new event API based on some existing one, s/he will say, "Huh, what's an + * LLReqID?" and look up this material. + * + * The hardest part about the convention is deciding where to store the + * ["reqid"] value. Ironically, LLReqID can't help with that: you must store + * an LLReqID instance in whatever storage will persist until the reply is + * sent. For example, if the request ultimately ends up using a Responder + * subclass, storing an LLReqID instance in the Responder works. + * + * @note + * The @em implementer of an event API must honor the ["reqid"] convention. + * However, the @em caller of an event API need only use it if s/he is sharing + * the same ["reply"] LLEventPump for two or more asynchronous event API + * requests. + * + * In most cases, it's far easier for the caller to instantiate a local + * LLEventStream and pass its name to the event API in question. Then it's + * perfectly reasonable not to set a ["reqid"] key in the request, ignoring + * the @c isUndefined() ["reqid"] value in the response. + */ +class LLReqID +{ +public: + /** + * If you have the request in hand at the time you instantiate the + * LLReqID, pass that request to extract its ["reqid"]. + */ + LLReqID(const LLSD& request): + mReqid(request["reqid"]) + {} + /// If you don't yet have the request, use setFrom() later. + LLReqID() {} + + /// Extract and store the ["reqid"] value from an incoming request. + void setFrom(const LLSD& request) + { + mReqid = request["reqid"]; + } + + /// Set ["reqid"] key into a pending response LLSD object. + void stamp(LLSD& response) const; + + /// Make a whole new response LLSD object with our ["reqid"]. + LLSD makeResponse() const + { + LLSD response; + stamp(response); + return response; + } + + /// Not really sure of a use case for this accessor... + LLSD getReqID() const { return mReqid; } + +private: + LLSD mReqid; +}; + /***************************************************************************** * Underpinnings *****************************************************************************/ diff --git a/indra/llmessage/llareslistener.cpp b/indra/llmessage/llareslistener.cpp index 8e1176cdd9..4bf375069d 100644 --- a/indra/llmessage/llareslistener.cpp +++ b/indra/llmessage/llareslistener.cpp @@ -24,6 +24,7 @@ #include "llares.h" #include "llerror.h" #include "llevents.h" +#include "llsdutil.h" LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares): mAres(llares), @@ -31,6 +32,8 @@ LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares): obtain(pumpname). listen("LLAresListener", boost::bind(&LLAresListener::process, this, _1))) { + // Insert an entry into our mDispatch map for every method we want to be + // able to invoke via this event API. mDispatch["rewriteURI"] = boost::bind(&LLAresListener::rewriteURI, this, _1); } @@ -60,9 +63,13 @@ bool LLAresListener::process(const LLSD& command) class UriRewriteResponder: public LLAres::UriRewriteResponder { public: - /// Specify the event pump name on which to send the reply - UriRewriteResponder(const std::string& pumpname): - mPumpName(pumpname) + /** + * Specify the request, containing the event pump name on which to send + * the reply. + */ + UriRewriteResponder(const LLSD& request): + mReqID(request), + mPumpName(request["reply"]) {} /// Called by base class with results. This is called in both the @@ -76,33 +83,27 @@ public: { result.append(*ui); } + // This call knows enough to avoid trying to insert a map key into an + // LLSD array. It's there so that if, for any reason, we ever decide + // to change the response from array to map, it will Just Start Working. + mReqID.stamp(result); LLEventPumps::instance().obtain(mPumpName).post(result); } private: + LLReqID mReqID; const std::string mPumpName; }; void LLAresListener::rewriteURI(const LLSD& data) { - const std::string uri(data["uri"]); - const std::string reply(data["reply"]); + static LLSD required(LLSD().insert("uri", LLSD()).insert("reply", LLSD())); // Validate that the request is well-formed - if (uri.empty() || reply.empty()) + std::string mismatch(llsd_matches(required, data)); + if (! mismatch.empty()) { - LL_ERRS("LLAresListener") << "rewriteURI request missing"; - std::string separator; - if (uri.empty()) - { - LL_CONT << " 'uri'"; - separator = " and"; - } - if (reply.empty()) - { - LL_CONT << separator << " 'reply'"; - } - LL_CONT << LL_ENDL; + LL_ERRS("LLAresListener") << "bad rewriteURI request: " << mismatch << LL_ENDL; } // Looks as though we have what we need; issue the request - mAres->rewriteURI(uri, new UriRewriteResponder(reply)); + mAres->rewriteURI(data["uri"], new UriRewriteResponder(data)); } diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp index f663268466..ad6b8284aa 100644 --- a/indra/llmessage/llsdmessage.cpp +++ b/indra/llmessage/llsdmessage.cpp @@ -68,6 +68,7 @@ bool LLSDMessage::httpListener(const LLSD& request) } LLHTTPClient::post(url, payload, new LLSDMessage::EventResponder(LLEventPumps::instance(), + request, url, "POST", reply, error), LLSD(), // headers timeout); @@ -81,7 +82,9 @@ void LLSDMessage::EventResponder::result(const LLSD& data) // to the pump whose name is "". if (! mReplyPump.empty()) { - mPumps.obtain(mReplyPump).post(data); + LLSD response(data); + mReqID.stamp(response); + mPumps.obtain(mReplyPump).post(response); } else // default success handling { @@ -98,7 +101,7 @@ void LLSDMessage::EventResponder::error(U32 status, const std::string& reason, c // explicit pump name. if (! mErrorPump.empty()) { - LLSD info; + LLSD info(mReqID.makeResponse()); info["target"] = mTarget; info["message"] = mMessage; info["status"] = LLSD::Integer(status); diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h index 8ae9451243..672da6d3a6 100644 --- a/indra/llmessage/llsdmessage.h +++ b/indra/llmessage/llsdmessage.h @@ -121,9 +121,11 @@ private: * (e.g. "POST") as @a message. */ EventResponder(LLEventPumps& pumps, + const LLSD& request, const std::string& target, const std::string& message, const std::string& replyPump, const std::string& errorPump): mPumps(pumps), + mReqID(request), mTarget(target), mMessage(message), mReplyPump(replyPump), @@ -135,6 +137,7 @@ private: private: LLEventPumps& mPumps; + LLReqID mReqID; const std::string mTarget, mMessage, mReplyPump, mErrorPump; }; diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp index b8306d0fd9..215a3806f8 100644 --- a/indra/llmessage/tests/llareslistener_test.cpp +++ b/indra/llmessage/tests/llareslistener_test.cpp @@ -149,7 +149,9 @@ namespace tut { threw = e.what(); } - ensure_contains("LLAresListener bad op", threw, "missing 'uri' and 'reply'"); + ensure_contains("LLAresListener bad req", threw, "missing"); + ensure_contains("LLAresListener bad req", threw, "reply"); + ensure_contains("LLAresListener bad req", threw, "uri"); } template<> template<> @@ -169,7 +171,9 @@ namespace tut { threw = e.what(); } - ensure_contains("LLAresListener bad op", threw, "missing 'uri'"); + ensure_contains("LLAresListener bad req", threw, "missing"); + ensure_contains("LLAresListener bad req", threw, "uri"); + ensure_does_not_contain("LLAresListener bad req", threw, "reply"); } template<> template<> @@ -189,6 +193,8 @@ namespace tut { threw = e.what(); } - ensure_contains("LLAresListener bad op", threw, "missing 'reply'"); + ensure_contains("LLAresListener bad req", threw, "missing"); + ensure_contains("LLAresListener bad req", threw, "reply"); + ensure_does_not_contain("LLAresListener bad req", threw, "uri"); } } diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp index 3277da8930..0a41ad614e 100644 --- a/indra/newview/llcapabilitylistener.cpp +++ b/indra/newview/llcapabilitylistener.cpp @@ -90,6 +90,7 @@ bool LLCapabilityListener::capListener(const LLSD& request) // This capability is supported by the region to which we're talking. LLHTTPClient::post(url, payload, new LLSDMessage::EventResponder(LLEventPumps::instance(), + request, mProvider.getDescription(), cap, reply, error), LLSD(), // headers diff --git a/indra/newview/llxmlrpclistener.cpp b/indra/newview/llxmlrpclistener.cpp index 2821e6c59f..71e2427c99 100644 --- a/indra/newview/llxmlrpclistener.cpp +++ b/indra/newview/llxmlrpclistener.cpp @@ -217,6 +217,7 @@ public: /// populate an XMLRPC_REQUEST and an associated LLXMLRPCTransaction. Send /// the request. Poller(const LLSD& command): + mReqID(command), mUri(command["uri"]), mMethod(command["method"]), mReplyPump(command["reply"]) @@ -325,7 +326,7 @@ public: curlcode = CURLcode(curlint); } - LLSD data; + LLSD data(mReqID.makeResponse()); data["status"] = sStatusMapper.lookup(status); data["errorcode"] = sCURLcodeMapper.lookup(curlcode); data["error"] = ""; @@ -476,6 +477,7 @@ private: return responses; } + const LLReqID mReqID; const std::string mUri; const std::string mMethod; const std::string mReplyPump; -- cgit v1.3 From dc3833f31b8a20220ddb1775e1625c016c397435 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 19 Jun 2009 00:17:30 +0000 Subject: DEV-31980: extract dispatch-by-string-name logic from LLAresListener to new LLEventDispatcher and LLDispatchListener classes. See LLAresListener for example usage. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/lleventdispatcher.cpp | 123 ++++++++++++++++++++++++++ indra/llcommon/lleventdispatcher.h | 108 ++++++++++++++++++++++ indra/llmessage/llareslistener.cpp | 46 ++-------- indra/llmessage/llareslistener.h | 14 +-- indra/llmessage/tests/llareslistener_test.cpp | 2 +- 6 files changed, 242 insertions(+), 53 deletions(-) create mode 100644 indra/llcommon/lleventdispatcher.cpp create mode 100644 indra/llcommon/lleventdispatcher.h (limited to 'indra/llmessage') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index e920475f54..88acb70b8a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -37,6 +37,7 @@ set(llcommon_SOURCE_FILES llerrorthread.cpp llevent.cpp lleventcoro.cpp + lleventdispatcher.cpp lleventfilter.cpp llevents.cpp llfasttimer.cpp @@ -126,6 +127,7 @@ set(llcommon_HEADER_FILES llerrorthread.h llevent.h lleventcoro.h + lleventdispatcher.h lleventfilter.h llevents.h lleventemitter.h diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 0000000000..2dbd59b156 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -0,0 +1,123 @@ +/** + * @file lleventdispatcher.cpp + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Implementation for lleventdispatcher. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventdispatcher.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "llsdutil.h" + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + mDesc(desc), + mKey(key) +{ +} + +LLEventDispatcher::~LLEventDispatcher() +{ +} + +/// Register a callable by name +void LLEventDispatcher::add(const std::string& name, const Callable& callable, const LLSD& required) +{ + mDispatch[name] = DispatchMap::mapped_type(callable, required); +} + +void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << classname << " is not a subclass " + << "of LLEventDispatcher" << LL_ENDL; +} + +/// Unregister a callable +bool LLEventDispatcher::remove(const std::string& name) +{ + DispatchMap::iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + return false; + } + mDispatch.erase(found); + return true; +} + +/// Call a registered callable with an explicitly-specified name. If no +/// such callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +{ + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name + << "' not found" << LL_ENDL; + } +} + +/// Extract the @a key value from the incoming @a event, and call the +/// callable whose name is specified by that map @a key. If no such +/// callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const LLSD& event) const +{ + // This could/should be implemented in terms of the two-arg overload. + // However -- we can produce a more informative error message. + std::string name(event[mKey]); + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey + << " value '" << name << "'" << LL_ENDL; + } +} + +bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + // The reason we only return false, leaving it up to our caller to die + // with LL_ERRS, is that different callers have different amounts of + // available information. + return false; + } + // Found the name, so it's plausible to even attempt the call. But first, + // validate the syntax of the event itself. + std::string mismatch(llsd_matches(found->second.second, event)); + if (! mismatch.empty()) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name + << "': bad request: " << mismatch << LL_ENDL; + } + // Event syntax looks good, go for it! + (found->second.first)(event); + return true; // tell caller we were able to call +} + +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): + LLEventDispatcher(pumpname, key), + mPump(pumpname, true), // allow tweaking for uniqueness + mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) +{ +} + +bool LLDispatchListener::process(const LLSD& event) +{ + (*this)(event); + return false; +} diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 0000000000..d75055fd6f --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -0,0 +1,108 @@ +/** + * @file lleventdispatcher.h + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Central mechanism for dispatching events by string name. This is + * useful when you have a single LLEventPump listener on which you can + * request different operations, vs. instantiating a different + * LLEventPump for each such operation. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTDISPATCHER_H) +#define LL_LLEVENTDISPATCHER_H + +#include +#include +#include +#include +#include +#include "llevents.h" + +class LLSD; + +/** + * Given an LLSD map, examine a string-valued key and call a corresponding + * callable. This class is designed to be contained by an LLEventPump + * listener class that will register some of its own methods, though any + * callable can be used. + */ +class LLEventDispatcher +{ +public: + LLEventDispatcher(const std::string& desc, const std::string& key); + virtual ~LLEventDispatcher(); + + /// Accept any C++ callable, typically a boost::bind() expression + typedef boost::function Callable; + + /// Register a @a callable by @a name. The optional @a required parameter + /// is used to validate the structure of each incoming event (see + /// llsd_matches()). + void add(const std::string& name, const Callable& callable, const LLSD& required=LLSD()); + + /// Special case: a subclass of this class can register a @a method + /// without explicitly specifying the boost::bind() expression. + /// The optional @a required parameter is used to validate the structure + /// of each incoming event (see llsd_matches()). + template + void add(const std::string& name, void (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, boost::bind(method, downcast, _1), required); + } + } + + /// Unregister a callable + bool remove(const std::string& name); + + /// Call a registered callable with an explicitly-specified name. If no + /// such callable exists, die with LL_ERRS. If the @a event fails to match + /// the @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const std::string& name, const LLSD& event) const; + + /// Extract the @a key value from the incoming @a event, and call the + /// callable whose name is specified by that map @a key. If no such + /// callable exists, die with LL_ERRS. If the @a event fails to match the + /// @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const LLSD& event) const; + +private: + void addFail(const std::string& name, const std::string& classname) const; + /// try to dispatch, return @c true if success + bool attemptCall(const std::string& name, const LLSD& event) const; + + std::string mDesc, mKey; + typedef std::map > DispatchMap; + DispatchMap mDispatch; +}; + +/** + * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class + * that contains (or derives from) LLDispatchListener need only specify the + * LLEventPump name and dispatch key, and add() its methods. Incoming events + * will automatically be dispatched. + */ +class LLDispatchListener: public LLEventDispatcher +{ +public: + LLDispatchListener(const std::string& pumpname, const std::string& key); + +private: + bool process(const LLSD& event); + + LLEventStream mPump; + LLTempBoundListener mBoundListener; +}; + +#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llmessage/llareslistener.cpp b/indra/llmessage/llareslistener.cpp index 4bf375069d..a8beb8cbde 100644 --- a/indra/llmessage/llareslistener.cpp +++ b/indra/llmessage/llareslistener.cpp @@ -9,10 +9,6 @@ * $/LicenseInfo$ */ -#if LL_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - // Precompiled header #include "linden_common.h" // associated header @@ -27,35 +23,13 @@ #include "llsdutil.h" LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares): - mAres(llares), - mBoundListener(LLEventPumps::instance(). - obtain(pumpname). - listen("LLAresListener", boost::bind(&LLAresListener::process, this, _1))) -{ - // Insert an entry into our mDispatch map for every method we want to be - // able to invoke via this event API. - mDispatch["rewriteURI"] = boost::bind(&LLAresListener::rewriteURI, this, _1); -} - -bool LLAresListener::process(const LLSD& command) + LLDispatchListener(pumpname, "op"), + mAres(llares) { - const std::string op(command["op"]); - // Look up the requested operation. - DispatchMap::const_iterator found = mDispatch.find(op); - if (found == mDispatch.end()) - { - // There's no feedback other than our own reply. If somebody asks - // for an operation that's not supported (perhaps because of a - // typo?), unless we holler loudly, the request will be silently - // ignored. Throwing a tantrum on such errors will hopefully make - // this product more robust. - LL_ERRS("LLAresListener") << "Unsupported request " << op << LL_ENDL; - return false; - } - // Having found the operation, call it. - found->second(command); - // Conventional LLEventPump listener return - return false; + // add() every method we want to be able to invoke via this event API. + // Optional third parameter validates expected LLSD request structure. + add("rewriteURI", &LLAresListener::rewriteURI, + LLSD().insert("uri", LLSD()).insert("reply", LLSD())); } /// This UriRewriteResponder subclass packages returned URIs as an LLSD @@ -97,13 +71,5 @@ private: void LLAresListener::rewriteURI(const LLSD& data) { - static LLSD required(LLSD().insert("uri", LLSD()).insert("reply", LLSD())); - // Validate that the request is well-formed - std::string mismatch(llsd_matches(required, data)); - if (! mismatch.empty()) - { - LL_ERRS("LLAresListener") << "bad rewriteURI request: " << mismatch << LL_ENDL; - } - // Looks as though we have what we need; issue the request mAres->rewriteURI(data["uri"], new UriRewriteResponder(data)); } diff --git a/indra/llmessage/llareslistener.h b/indra/llmessage/llareslistener.h index 8835440c5d..bf093b3d3d 100644 --- a/indra/llmessage/llareslistener.h +++ b/indra/llmessage/llareslistener.h @@ -14,33 +14,23 @@ #if ! defined(LL_LLARESLISTENER_H) #define LL_LLARESLISTENER_H -#include -#include -#include -#include "llevents.h" +#include "lleventdispatcher.h" class LLAres; class LLSD; /// Listen on an LLEventPump with specified name for LLAres request events. -class LLAresListener +class LLAresListener: public LLDispatchListener { public: /// Specify the pump name on which to listen, and bind the LLAres instance /// to use (e.g. gAres) LLAresListener(const std::string& pumpname, LLAres* llares); - /// Handle request events on the event pump specified at construction time - bool process(const LLSD& command); - private: /// command["op"] == "rewriteURI" void rewriteURI(const LLSD& data); - typedef boost::function Callable; - typedef std::map DispatchMap; - DispatchMap mDispatch; - LLTempBoundListener mBoundListener; LLAres* mAres; }; diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp index 215a3806f8..ac4886ccf4 100644 --- a/indra/llmessage/tests/llareslistener_test.cpp +++ b/indra/llmessage/tests/llareslistener_test.cpp @@ -130,7 +130,7 @@ namespace tut { threw = e.what(); } - ensure_contains("LLAresListener bad op", threw, "Unsupported"); + ensure_contains("LLAresListener bad op", threw, "bad"); } template<> template<> -- cgit v1.3 From fae273975bc811d9c8c7b67df44653f483fe22f6 Mon Sep 17 00:00:00 2001 From: "palange@becane.lindenlab.com" Date: Tue, 23 Jun 2009 11:29:55 -0700 Subject: Fixed an "ADD_BUILD_TEST" leftover from the initial merge. --- indra/llmessage/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 29e7aed898..73140e60e4 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -223,11 +223,11 @@ IF (NOT LINUX AND VIEWER) # llhttpclientadapter.cpp lltrustedmessageservice.cpp lltemplatemessagedispatcher.cpp + # Commented out - see rationale at bottom of newview's build file + poppy 2009-06-05 + # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! + # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") + # llareslistener.cpp ) LL_ADD_PROJECT_UNIT_TESTS(llmessage "${llmessage_TEST_SOURCE_FILES}") - # Commented out - see rationale at bottom of newview's build file + poppy 2009-06-05 - # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! - # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") - ADD_BUILD_TEST(llareslistener llmessage) ENDIF (NOT LINUX AND VIEWER) -- cgit v1.3 From 07a05e2c0a14bb54ace65d63b55ebc8bb2a4efee Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 23 Jun 2009 18:16:57 -0400 Subject: Incomplete attempt to clean up Mercurial branch build --- indra/llcommon/llevents.h | 203 ++++++++++++++++++++++++++--------- indra/llmessage/CMakeLists.txt | 2 +- indra/newview/llappviewer.cpp | 2 +- indra/newview/llinventorybridge.h | 159 ---------------------------- indra/newview/llinventorymodel.cpp | 210 ++++++++++++++++++------------------- indra/newview/llpanelplaceinfo.cpp | 1 + 6 files changed, 259 insertions(+), 318 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 2f6515a4cb..c5a27ab68e 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -28,13 +27,9 @@ #include #include // noncopyable #include -#include #include #include // reference_wrapper #include -#include -#include -#include #include #include #include "llsd.h" @@ -111,6 +106,9 @@ typedef LLStandardSignal::slot_type LLEventListener; /// Result of registering a listener, supports connected(), /// disconnect() and blocked() typedef boost::signals2::connection LLBoundListener; +/// Storing an LLBoundListener in LLTempBoundListener will disconnect the +/// referenced listener when the LLTempBoundListener instance is destroyed. +typedef boost::signals2::scoped_connection LLTempBoundListener; /** * A common idiom for event-based code is to accept either a callable -- @@ -127,7 +125,7 @@ typedef boost::signals2::connection LLBoundListener; * LLListenerOrPumpName::Empty. Test for this condition beforehand using * either if (param) or if (! param). */ -class LLListenerOrPumpName +class LL_COMMON_API LLListenerOrPumpName { public: /// passing string name of LLEventPump @@ -174,13 +172,13 @@ private: /***************************************************************************** * LLEventPumps *****************************************************************************/ -class LLEventPump; +class LL_COMMON_API LLEventPump; /** * LLEventPumps is a Singleton manager through which one typically accesses * this subsystem. */ -class LLEventPumps: public LLSingleton +class LL_COMMON_API LLEventPumps: public LLSingleton { friend class LLSingleton; public: @@ -254,14 +252,62 @@ namespace LLEventDetail const ConnectFunc& connect_func); } // namespace LLEventDetail +/***************************************************************************** +* LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1)). This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * LLEventPump::listen(). + * - The @c MySubclass object begins destruction. ~MySubclass() + * runs, destroying state specific to the subclass. (For instance, a + * Foo* data member is deleted but not zeroed.) + * - The listening method will not be disconnected until + * ~LLEventTrackable() runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the Foo* pointer that was + * deleted but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with boost::shared_ptr. + * Passing LLEventPump::listen() a boost::bind() expression + * involving a boost::weak_ptr is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + /***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses LLEventStream and LLEventQueue. + * + * @NOTE + * LLEventPump derives from LLEventTrackable so that when you "chain" + * LLEventPump instances together, they will automatically disconnect on + * destruction. Please see LLEventTrackable documentation for situations in + * which this may be perilous across threads. */ -class LLEventPump: boost::noncopyable +class LL_COMMON_API LLEventPump: public LLEventTrackable { public: /** @@ -364,10 +410,22 @@ public: * themselves. listen() can throw any ListenError; see ListenError * subclasses. * - * If (as is typical) you pass a boost::bind() expression, - * listen() will inspect the components of that expression. If a bound - * object matches any of several cases, the connection will automatically - * be disconnected when that object is destroyed. + * The listener name must be unique among active listeners for this + * LLEventPump, else you get DupListenerName. If you don't care to invent + * a name yourself, use inventName(). (I was tempted to recognize e.g. "" + * and internally generate a distinct name for that case. But that would + * handle badly the scenario in which you want to add, remove, re-add, + * etc. the same listener: each new listen() call would necessarily + * perform a new dependency sort. Assuming you specify the same + * after/before lists each time, using inventName() when you first + * instantiate your listener, then passing the same name on each listen() + * call, allows us to optimize away the second and subsequent dependency + * sorts. + * + * If (as is typical) you pass a boost::bind() expression as @a + * listener, listen() will inspect the components of that expression. If a + * bound object matches any of several cases, the connection will + * automatically be disconnected when that object is destroyed. * * * You bind a boost::weak_ptr. * * Binding a boost::shared_ptr that way would ensure that the @@ -429,6 +487,9 @@ public: /// query virtual bool enabled() const { return mEnabled; } + /// Generate a distinct name for a listener -- see listen() + static std::string inventName(const std::string& pfx="listener"); + private: friend class LLEventPumps; /// flush queued events @@ -467,7 +528,7 @@ protected: * LLEventStream is a thin wrapper around LLStandardSignal. Posting an * event immediately calls all registered listeners. */ -class LLEventStream: public LLEventPump +class LL_COMMON_API LLEventStream: public LLEventPump { public: LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -484,7 +545,7 @@ public: * LLEventQueue isa LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ -class LLEventQueue: public LLEventPump +class LL_COMMON_API LLEventQueue: public LLEventPump { public: LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -503,47 +564,89 @@ private: }; /***************************************************************************** -* LLEventTrackable and underpinnings +* LLReqID *****************************************************************************/ /** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1)). This will implicitly disconnect when the object - * referenced by @c instance is destroyed. + * This class helps the implementer of a given event API to honor the + * ["reqid"] convention. By this convention, each event API stamps into its + * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if + * any, from the corresponding request. + * + * This supports an (atypical, but occasionally necessary) use case in which + * two or more asynchronous requests are multiplexed onto the same ["reply"] + * LLEventPump. Since the response events could arrive in arbitrary order, the + * caller must be able to demux them. It does so by matching the ["reqid"] + * value in each response with the ["reqid"] value in the corresponding + * request. + * + * It is the caller's responsibility to ensure distinct ["reqid"] values for + * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: + * the "namespace" of unique ["reqid"] values is simply the set of requests + * specifying the same ["reply"] LLEventPump name. + * + * Making a given event API echo the request's ["reqid"] into the response is + * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a + * place to put these comments. We hope that each time a coder implements a + * new event API based on some existing one, s/he will say, "Huh, what's an + * LLReqID?" and look up this material. + * + * The hardest part about the convention is deciding where to store the + * ["reqid"] value. Ironically, LLReqID can't help with that: you must store + * an LLReqID instance in whatever storage will persist until the reply is + * sent. For example, if the request ultimately ends up using a Responder + * subclass, storing an LLReqID instance in the Responder works. * * @note - * LLEventTrackable doesn't address a couple of cases: - * * Object destroyed during call - * - You enter a slot call in thread A. - * - Thread B destroys the object, which of course disconnects it from any - * future slot calls. - * - Thread A's call uses 'this', which now refers to a defunct object. - * Undefined behavior results. - * * Call during destruction - * - @c MySubclass is derived from LLEventTrackable. - * - @c MySubclass registers one of its own methods using - * LLEventPump::listen(). - * - The @c MySubclass object begins destruction. ~MySubclass() - * runs, destroying state specific to the subclass. (For instance, a - * Foo* data member is deleted but not zeroed.) - * - The listening method will not be disconnected until - * ~LLEventTrackable() runs. - * - Before we get there, another thread posts data to the @c LLEventPump - * instance, calling the @c MySubclass method. - * - The method in question relies on valid @c MySubclass state. (For - * instance, it attempts to dereference the Foo* pointer that was - * deleted but not zeroed.) - * - Undefined behavior results. - * If you suspect you may encounter any such scenario, you're better off - * managing the lifespan of your object with boost::shared_ptr. - * Passing LLEventPump::listen() a boost::bind() expression - * involving a boost::weak_ptr is recognized specially, engaging - * thread-safe Boost.Signals2 machinery. + * The @em implementer of an event API must honor the ["reqid"] convention. + * However, the @em caller of an event API need only use it if s/he is sharing + * the same ["reply"] LLEventPump for two or more asynchronous event API + * requests. + * + * In most cases, it's far easier for the caller to instantiate a local + * LLEventStream and pass its name to the event API in question. Then it's + * perfectly reasonable not to set a ["reqid"] key in the request, ignoring + * the @c isUndefined() ["reqid"] value in the response. */ -typedef boost::signals2::trackable LLEventTrackable; +class LLReqID +{ +public: + /** + * If you have the request in hand at the time you instantiate the + * LLReqID, pass that request to extract its ["reqid"]. + */ + LLReqID(const LLSD& request): + mReqid(request["reqid"]) + {} + /// If you don't yet have the request, use setFrom() later. + LLReqID() {} + + /// Extract and store the ["reqid"] value from an incoming request. + void setFrom(const LLSD& request) + { + mReqid = request["reqid"]; + } + + /// Set ["reqid"] key into a pending response LLSD object. + void stamp(LLSD& response) const; + + /// Make a whole new response LLSD object with our ["reqid"]. + LLSD makeResponse() const + { + LLSD response; + stamp(response); + return response; + } + /// Not really sure of a use case for this accessor... + LLSD getReqID() const { return mReqid; } + +private: + LLSD mReqid; +}; + +/***************************************************************************** +* Underpinnings +*****************************************************************************/ /** * We originally provided a suite of overloaded * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 29e7aed898..35e6f9d640 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -229,5 +229,5 @@ IF (NOT LINUX AND VIEWER) # Commented out - see rationale at bottom of newview's build file + poppy 2009-06-05 # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") - ADD_BUILD_TEST(llareslistener llmessage) +# ADD_BUILD_TEST(llareslistener llmessage) ENDIF (NOT LINUX AND VIEWER) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 48cb3babfa..3fc3c8e382 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3652,7 +3652,7 @@ void LLAppViewer::idleShutdown() if (!saved_teleport_history) { saved_teleport_history = true; - LLTeleportHistory::getInstance()->save(); + LLTeleportHistory::getInstance()->dump(); LLLocationHistory::getInstance()->save(); // *TODO: find a better place for doing this return; } diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index b55a6a658b..3958f7e9c2 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -562,165 +562,6 @@ protected: EWearableType mWearableType; }; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInvFVBridgeAction (& it's derived classes) -// -// This is an implementation class to be able to -// perform action to view inventory items. -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInvFVBridgeAction -{ -public: - // This method is a convenience function which creates the correct - // type of bridge action based on some basic information - static LLInvFVBridgeAction* createAction(LLAssetType::EType asset_type, - const LLUUID& uuid,LLInventoryModel* model); - - static void doAction(LLAssetType::EType asset_type, - const LLUUID& uuid,LLInventoryModel* model); - - virtual void doIt() { }; - virtual ~LLInvFVBridgeAction(){}//need this because of warning on OSX -protected: - LLInvFVBridgeAction(const LLUUID& id,LLInventoryModel* model):mUUID(id),mModel(model){} - - LLViewerInventoryItem* getItem() const; -protected: - const LLUUID& mUUID; // item id - LLInventoryModel* mModel; - -}; - - - -class LLTextureBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLTextureBridgeAction(){} -protected: - LLTextureBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLSoundBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLSoundBridgeAction(){} -protected: - LLSoundBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLLandmarkBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLLandmarkBridgeAction(){} -protected: - LLLandmarkBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLCallingCardBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLCallingCardBridgeAction(){} -protected: - LLCallingCardBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLNotecardBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLNotecardBridgeAction(){} -protected: - LLNotecardBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLGestureBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLGestureBridgeAction(){} -protected: - LLGestureBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLAnimationBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLAnimationBridgeAction(){} -protected: - LLAnimationBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLObjectBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLObjectBridgeAction(){} -protected: - LLObjectBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLLSLTextBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() ; - virtual ~LLLSLTextBridgeAction(){} -protected: - LLLSLTextBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - -}; - - -class LLWearableBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt(); - virtual ~LLWearableBridgeAction(){} -protected: - LLWearableBridgeAction(const LLUUID& id,LLInventoryModel* model):LLInvFVBridgeAction(id,model){} - - - BOOL isInTrash() const; - // return true if the item is in agent inventory. if false, it - // must be lost or in the inventory library. - BOOL isAgentInventory() const; - - void wearOnAvatar(); - -}; - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLInvFVBridgeAction (& it's derived classes) // diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 9177d51d5c..bac02e30bf 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1844,7 +1844,7 @@ bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const } bool LLInventoryModel::loadSkeleton( - const LLInventoryModel::options_t& options, + const LLSD& options, const LLUUID& owner_id) { lldebugs << "importing inventory skeleton for " << owner_id << llendl; @@ -1857,44 +1857,41 @@ bool LLInventoryModel::loadSkeleton( LLUUID id; LLAssetType::EType preferred_type; bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - LLPointer cat = new LLViewerInventoryCategory(owner_id); - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator skel; - skel = (*it).find("name"); - if(skel == no_response) goto clean_cat; - cat->rename(std::string((*skel).second)); - skel = (*it).find("folder_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second); - // if an id is null, it locks the viewer. - if(id.isNull()) goto clean_cat; - cat->setUUID(id); - skel = (*it).find("parent_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second); - cat->setParent(id); - skel = (*it).find("type_default"); - if(skel == no_response) - { - preferred_type = LLAssetType::AT_NONE; + + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD folder_id = (*it)["folder_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD version = (*it)["version"]; + if(name.isDefined() + && folder_id.isDefined() + && parent_id.isDefined() + && version.isDefined() + && folder_id.asUUID().notNull() // if an id is null, it locks the viewer. + ) + { + LLPointer cat = new LLViewerInventoryCategory(owner_id); + cat->rename(name.asString()); + cat->setUUID(folder_id.asUUID()); + cat->setParent(parent_id.asUUID()); + + LLAssetType::EType preferred_type = LLAssetType::AT_NONE; + LLSD type_default = (*it)["type_default"]; + if(type_default.isDefined()) + { + preferred_type = (LLAssetType::EType)type_default.asInteger(); + } + cat->setPreferredType(preferred_type); + cat->setVersion(version.asInteger()); + temp_cats.insert(cat); } else { - S32 t = atoi((*skel).second.c_str()); - preferred_type = (LLAssetType::EType)t; + llwarns << "Unable to import near " << name.asString() << llendl; + rv = false; } - cat->setPreferredType(preferred_type); - skel = (*it).find("version"); - if(skel == no_response) goto clean_cat; - cat->setVersion(atoi((*skel).second.c_str())); - temp_cats.insert(cat); - continue; - clean_cat: - llwarns << "Unable to import near " << cat->getName() << llendl; - rv = false; - //delete cat; // automatic when cat is reasigned or destroyed } S32 cached_category_count = 0; @@ -2053,85 +2050,84 @@ bool LLInventoryModel::loadSkeleton( return rv; } -bool LLInventoryModel::loadMeat( - const LLInventoryModel::options_t& options, const LLUUID& owner_id) +bool LLInventoryModel::loadMeat(const LLSD& options, const LLUUID& owner_id) { llinfos << "importing inventory for " << owner_id << llendl; - LLPermissions default_perm; - default_perm.init(LLUUID::null, owner_id, LLUUID::null, LLUUID::null); - LLPointer item; - LLUUID id; - LLAssetType::EType type; - LLInventoryType::EType inv_type; bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - item = new LLViewerInventoryItem; - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator meat; - meat = (*it).find("name"); - if(meat == no_response) goto clean_item; - item->rename(std::string((*meat).second)); - meat = (*it).find("item_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - item->setUUID(id); - meat = (*it).find("parent_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - item->setParent(id); - meat = (*it).find("type"); - if(meat == no_response) goto clean_item; - type = (LLAssetType::EType)atoi((*meat).second.c_str()); - item->setType(type); - meat = (*it).find("inv_type"); - if(meat != no_response) - { - inv_type = (LLInventoryType::EType)atoi((*meat).second.c_str()); - item->setInventoryType(inv_type); - } - meat = (*it).find("data_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second); - if(LLAssetType::AT_CALLINGCARD == type) - { - LLPermissions perm; - perm.init(id, owner_id, LLUUID::null, LLUUID::null); - item->setPermissions(perm); - } - else - { - meat = (*it).find("perm_mask"); - if(meat != no_response) - { - PermissionMask perm_mask = atoi((*meat).second.c_str()); - default_perm.initMasks( + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD item_id = (*it)["item_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD asset_type = (*it)["type"]; + LLSD data_id = (*it)["data_id"]; + if(name.isDefined() + && item_id.isDefined() + && parent_id.isDefined() + && asset_type.isDefined() + && data_id.isDefined()) + { + LLPointer item = new LLViewerInventoryItem; + item->rename(name.asString()); + item->setUUID(item_id.asUUID()); + item->setParent(parent_id.asUUID()); + LLAssetType::EType type = (LLAssetType::EType)asset_type.asInteger(); + item->setType(type); + + LLSD llsd_inv_type = (*it)["inv_type"]; + if(llsd_inv_type.isDefined()) + { + LLInventoryType::EType inv_type = (LLInventoryType::EType)llsd_inv_type.asInteger(); + item->setInventoryType(inv_type); + } + + if(LLAssetType::AT_CALLINGCARD == type) + { + LLPermissions perm; + perm.init(data_id.asUUID(), owner_id, LLUUID::null, LLUUID::null); + item->setPermissions(perm); + } + else + { + LLPermissions default_perm; + default_perm.init(LLUUID::null, owner_id, LLUUID::null, LLUUID::null); + LLSD llsd_perm_mask = (*it)["perm_mask"]; + if(llsd_perm_mask.isDefined()) + { + PermissionMask perm_mask = llsd_perm_mask.asInteger(); + default_perm.initMasks( perm_mask, perm_mask, perm_mask, perm_mask, perm_mask); - } - else - { - default_perm.initMasks( - PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); - } - item->setPermissions(default_perm); - item->setAssetUUID(id); - } - meat = (*it).find("flags"); - if(meat != no_response) - { - item->setFlags(strtoul((*meat).second.c_str(), NULL, 0)); + } + else + { + default_perm.initMasks( + PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); + } + item->setPermissions(default_perm); + item->setAssetUUID(data_id.asUUID()); + } + + LLSD flags = (*it)["flags"]; + if(flags.isDefined()) + { + // Not sure how well LLSD.asInteger() maps to + // unsigned long - using strtoul() + item->setFlags(strtoul(flags.asString().c_str(), NULL, 0)); + } + + LLSD time = (*it)["time"]; + if(time.isDefined()) + { + item->setCreationDate(time.asInteger()); + } + addItem(item); } - meat = (*it).find("time"); - if(meat != no_response) + else { - item->setCreationDate(atoi((*meat).second.c_str())); + llwarns << "Unable to import near " << name.asString() << llendl; + rv = false; } - addItem(item); - continue; - clean_item: - llwarns << "Unable to import near " << item->getName() << llendl; - rv = false; - //delete item; // automatic when item is reassigned or destroyed } return rv; } diff --git a/indra/newview/llpanelplaceinfo.cpp b/indra/newview/llpanelplaceinfo.cpp index 3ed93e5598..951c223668 100644 --- a/indra/newview/llpanelplaceinfo.cpp +++ b/indra/newview/llpanelplaceinfo.cpp @@ -61,6 +61,7 @@ #include "lluictrlfactory.h" #include "llweb.h" #include "llsdutil.h" +#include "llsdutil_math.h" static LLRegisterPanelClassWrapper t_places("panel_landmark_info"); -- cgit v1.3 From 9b86d4c12f5f29d0bca5354d4e1ac6b5c828a327 Mon Sep 17 00:00:00 2001 From: "Mark Palange (Mani)" Date: Tue, 23 Jun 2009 17:58:51 -0700 Subject: Removed conflict markers (ie. <<<<<<<) in llmessage/CMakeLists.txt Copied snprintf declaration in llstring from login-api-3 --- indra/llcommon/llstring.h | 9 ++++++++- indra/llmessage/CMakeLists.txt | 9 +-------- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 91b706a5be..813e2656ae 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -521,7 +521,14 @@ std::string utf8str_removeCRLF(const std::string& utf8str); * formatted string. * */ -int safe_snprintf(char* str, size_t size, const char* format, ...); + +// Deal with the differeneces on Windows +namespace snprintf_hack +{ + LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...); +} + +using snprintf_hack::snprintf; /** * @brief Convert a wide string to std::string diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 3fa6454255..67a171dde6 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -229,12 +229,5 @@ IF (NOT LINUX AND VIEWER) # llareslistener.cpp ) LL_ADD_PROJECT_UNIT_TESTS(llmessage "${llmessage_TEST_SOURCE_FILES}") - -<<<<<<< local - # Commented out - see rationale at bottom of newview's build file + poppy 2009-06-05 - # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! - # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") -# ADD_BUILD_TEST(llareslistener llmessage) -======= ->>>>>>> other + ENDIF (NOT LINUX AND VIEWER) -- cgit v1.3 From e588d1f28419745ee1e1ee98dc1852e0364a4088 Mon Sep 17 00:00:00 2001 From: Christian Goetze Date: Wed, 1 Jul 2009 00:22:05 +0000 Subject: svn merge -r125825:125901 svn+ssh://svn.lindenlab.com/svn/user/cg/qar-1654 QAR-1654 merge completed. --- indra/develop.py | 4 +- indra/lib/python/indra/base/llsd.py | 2 +- indra/lib/python/indra/util/llsubprocess.py | 11 + indra/lib/python/indra/util/named_query.py | 4 +- indra/llcharacter/llkeyframestandmotion.cpp | 2 +- indra/llcommon/llkeythrottle.h | 57 ++++++ indra/llcommon/llstat.cpp | 51 ++++- indra/llcommon/llstat.h | 21 +- indra/llimage/llimagej2c.cpp | 48 ++++- indra/llimage/llimagej2c.h | 4 + indra/llinventory/llpermissions.cpp | 11 + indra/llinventory/llpermissions.h | 4 + indra/llmath/llquaternion.cpp | 6 +- indra/llmath/llquaternion.h | 34 +++- indra/llmessage/CMakeLists.txt | 2 + indra/llmessage/llcachename.cpp | 226 ++++++++++++++------- indra/llmessage/llcachename.h | 6 +- indra/llmessage/llhttpclientadapter.cpp | 2 +- indra/llmessage/llhttpclientadapter.h | 2 +- indra/llmessage/llhttpclientinterface.h | 2 +- indra/llmessage/llmessagesenderinterface.h | 2 +- indra/llmessage/llregionpresenceverifier.cpp | 86 ++++++-- indra/llmessage/llregionpresenceverifier.h | 39 ++-- indra/llmessage/llstoredmessage.cpp | 2 +- indra/llmessage/llstoredmessage.h | 2 +- indra/llmessage/llthrottle.cpp | 25 +++ indra/llmessage/llthrottle.h | 2 + indra/llmessage/llurlrequest.cpp | 54 +++-- indra/llmessage/llurlrequest.h | 8 + .../tests/llregionpresenceverifier_test.cpp | 111 ++++++++++ indra/llprimitive/llprimtexturelist.cpp | 14 +- indra/llvfs/llpidlock.cpp | 2 +- indra/lscript/lscript_byteformat.h | 2 +- indra/lscript/lscript_compile/lscript_tree.cpp | 29 ++- indra/lscript/lscript_compile/lscript_tree.h | 1 + indra/lscript/lscript_execute.h | 3 +- indra/lscript/lscript_execute/lscript_execute.cpp | 122 ++++------- indra/newview/llfloaterregioninfo.cpp | 2 +- indra/newview/llimview.cpp | 3 +- indra/newview/llmutelist.cpp | 2 +- indra/newview/llviewermessage.cpp | 10 +- indra/newview/llvoiceclient.cpp | 3 +- 42 files changed, 739 insertions(+), 284 deletions(-) create mode 100644 indra/llmessage/tests/llregionpresenceverifier_test.cpp (limited to 'indra/llmessage') diff --git a/indra/develop.py b/indra/develop.py index 1d7ac42c9c..b40e81bb07 100755 --- a/indra/develop.py +++ b/indra/develop.py @@ -451,9 +451,7 @@ class DarwinSetup(UnixSetup): targets = ' '.join(['-target ' + repr(t) for t in targets]) else: targets = '' - # cmd = ('xcodebuild -parallelizeTargets ' # parallelizeTargets is suspected of non-deterministic build failures. + poppy 2009-06-05 - cmd = ('xcodebuild ' - '-configuration %s %s %s' % + cmd = ('xcodebuild -configuration %s %s %s' % (self.build_type, ' '.join(opts), targets)) for d in self.build_dirs(): try: diff --git a/indra/lib/python/indra/base/llsd.py b/indra/lib/python/indra/base/llsd.py index 1190d88663..4527b115f9 100644 --- a/indra/lib/python/indra/base/llsd.py +++ b/indra/lib/python/indra/base/llsd.py @@ -238,7 +238,7 @@ class LLSDXMLFormatter(object): def MAP(self, v): return self.elt( 'map', - ''.join(["%s%s" % (self.elt('key', key), self.generate(value)) + ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) for key, value in v.items()])) typeof = type diff --git a/indra/lib/python/indra/util/llsubprocess.py b/indra/lib/python/indra/util/llsubprocess.py index c4c40739ec..7e0e115d14 100644 --- a/indra/lib/python/indra/util/llsubprocess.py +++ b/indra/lib/python/indra/util/llsubprocess.py @@ -90,6 +90,17 @@ all the output, and get the result. child.tochild.close() result = child.poll() if result != -1: + # At this point, the child process has exited and result + # is the return value from the process. Between the time + # we called select() and poll() the process may have + # exited so read all the data left on the child process + # stdout and stderr. + last = child.fromchild.read() + if last: + out.append(last) + last = child.childerr.read() + if last: + err.append(last) child.tochild.close() child.fromchild.close() child.childerr.close() diff --git a/indra/lib/python/indra/util/named_query.py b/indra/lib/python/indra/util/named_query.py index 693b483f79..5c19368240 100644 --- a/indra/lib/python/indra/util/named_query.py +++ b/indra/lib/python/indra/util/named_query.py @@ -48,8 +48,8 @@ from indra.base import llsd from indra.base import config DEBUG = False -NQ_FILE_SUFFIX = None -NQ_FILE_SUFFIX_LEN = None +NQ_FILE_SUFFIX = config.get('named-query-file-suffix', '.nq') +NQ_FILE_SUFFIX_LEN = len(NQ_FILE_SUFFIX) _g_named_manager = None diff --git a/indra/llcharacter/llkeyframestandmotion.cpp b/indra/llcharacter/llkeyframestandmotion.cpp index 1d42298f4d..1ae0ddeea0 100644 --- a/indra/llcharacter/llkeyframestandmotion.cpp +++ b/indra/llcharacter/llkeyframestandmotion.cpp @@ -190,7 +190,7 @@ BOOL LLKeyframeStandMotion::onUpdate(F32 time, U8* joint_mask) if (dot(mPelvisState->getJoint()->getWorldRotation(), mLastGoodPelvisRotation) < ROTATION_THRESHOLD) { mLastGoodPelvisRotation = mPelvisState->getJoint()->getWorldRotation(); - mLastGoodPelvisRotation.normQuat(); + mLastGoodPelvisRotation.normalize(); mTrackAnkles = TRUE; } else if ((mCharacter->getCharacterPosition() - mLastGoodPosition).magVecSquared() > POSITION_THRESHOLD) diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index 873f50a65e..7544ab1d11 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -118,6 +118,63 @@ public: THROTTLE_BLOCKED, // rate exceed, block key }; + F64 getActionCount(const T& id) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl::getTime(); + } + else + { + now = LLKeyThrottleImpl::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl::EntryMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + + typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + } + + typename LLKeyThrottleImpl::Entry& curr = (*m.currMap)[id]; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + return averageCount; + } + // call each time the key wants use State noteAction(const T& id, S32 weight = 1) { diff --git a/indra/llcommon/llstat.cpp b/indra/llcommon/llstat.cpp index 291b019616..e650f911b7 100644 --- a/indra/llcommon/llstat.cpp +++ b/indra/llcommon/llstat.cpp @@ -43,7 +43,7 @@ // statics -BOOL LLPerfBlock::sStatsEnabled = FALSE; // Flag for detailed information +S32 LLPerfBlock::sStatsFlags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS; // Control what is being recorded LLPerfBlock::stat_map_t LLPerfBlock::sStatMap; // Map full path string to LLStatTime objects, tracks all active objects std::string LLPerfBlock::sCurrentStatPath = ""; // Something like "/total_time/physics/physics step" @@ -129,6 +129,7 @@ bool LLStatsConfigFile::loadFile() F32 duration = 0.f; F32 interval = 0.f; + S32 flags = LLPerfBlock::LLSTATS_BASIC_STATS; const char * w = "duration"; if (stats_config.has(w)) @@ -140,8 +141,18 @@ bool LLStatsConfigFile::loadFile() { interval = (F32)stats_config[w].asReal(); } + w = "flags"; + if (stats_config.has(w)) + { + flags = (S32)stats_config[w].asInteger(); + if (flags == LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS && + duration > 0) + { // No flags passed in, but have a duration, so reset to basic stats + flags = LLPerfBlock::LLSTATS_BASIC_STATS; + } + } - mStatsp->setReportPerformanceDuration( duration ); + mStatsp->setReportPerformanceDuration( duration, flags ); mStatsp->setReportPerformanceInterval( interval ); if ( duration > 0 ) @@ -253,13 +264,14 @@ void LLPerfStats::dumpIntervalPerformanceStats() } } -// Set length of performance stat recording -void LLPerfStats::setReportPerformanceDuration( F32 seconds ) +// Set length of performance stat recording. +// If turning stats on, caller must provide flags +void LLPerfStats::setReportPerformanceDuration( F32 seconds, S32 flags /* = LLSTATS_NO_OPTIONAL_STATS */ ) { if ( seconds <= 0.f ) { mReportPerformanceStatEnd = 0.0; - LLPerfBlock::setStatsEnabled( FALSE ); + LLPerfBlock::setStatsFlags(LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS); // Make sure all recording is off mFrameStatsFile.close(); LLPerfBlock::clearDynamicStats(); } @@ -268,8 +280,8 @@ void LLPerfStats::setReportPerformanceDuration( F32 seconds ) mReportPerformanceStatEnd = LLFrameTimer::getElapsedSeconds() + ((F64) seconds); // Clear failure flag to try and create the log file once mFrameStatsFileFailure = FALSE; - LLPerfBlock::setStatsEnabled( TRUE ); mSkipFirstFrameStats = TRUE; // Skip the first report (at the end of this frame) + LLPerfBlock::setStatsFlags(flags); } } @@ -611,11 +623,26 @@ LLPerfBlock::LLPerfBlock(LLStatTime* stat ) : mPredefinedStat(stat), mDynamicSta } } -// Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key. -// These are also turned on or off via the switch passed in -LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat(NULL), mDynamicStat(NULL) +// Use this constructor for normal, optional LLPerfBlock time slices +LLPerfBlock::LLPerfBlock( const char* key ) : mPredefinedStat(NULL), mDynamicStat(NULL) { - if (!sStatsEnabled) return; + if ((sStatsFlags & LLSTATS_BASIC_STATS) == 0) + { // These are off unless the base set is enabled + return; + } + + initDynamicStat(key); +} + + +// Use this constructor for dynamically created LLPerfBlock time slices +// that are only enabled by specific control flags +LLPerfBlock::LLPerfBlock( const char* key1, const char* key2, S32 flags ) : mPredefinedStat(NULL), mDynamicStat(NULL) +{ + if ((sStatsFlags & flags) == 0) + { + return; + } if (NULL == key2 || strlen(key2) == 0) { @@ -629,10 +656,12 @@ LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat } } +// Set up the result data map if dynamic stats are enabled void LLPerfBlock::initDynamicStat(const std::string& key) { // Early exit if dynamic stats aren't enabled. - if (!sStatsEnabled) return; + if (sStatsFlags == LLSTATS_NO_OPTIONAL_STATS) + return; mLastPath = sCurrentStatPath; // Save and restore current path sCurrentStatPath += "/" + key; // Add key to current path diff --git a/indra/llcommon/llstat.h b/indra/llcommon/llstat.h index 61aaac45bf..e2e904bb06 100644 --- a/indra/llcommon/llstat.h +++ b/indra/llcommon/llstat.h @@ -192,14 +192,23 @@ public: // Use this constructor for pre-defined LLStatTime objects LLPerfBlock(LLStatTime* stat); - // Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key - LLPerfBlock( const char* key1, const char* key2 = NULL); + // Use this constructor for normal, optional LLPerfBlock time slices + LLPerfBlock( const char* key ); + // Use this constructor for dynamically created LLPerfBlock time slices + // that are only enabled by specific control flags + LLPerfBlock( const char* key1, const char* key2, S32 flags = LLSTATS_BASIC_STATS ); ~LLPerfBlock(); - static void setStatsEnabled( BOOL enable ) { sStatsEnabled = enable; }; - static S32 getStatsEnabled() { return sStatsEnabled; }; + enum + { // Stats bitfield flags + LLSTATS_NO_OPTIONAL_STATS = 0x00, // No optional stats gathering, just pre-defined LLStatTime objects + LLSTATS_BASIC_STATS = 0x01, // Gather basic optional runtime stats + LLSTATS_SCRIPT_FUNCTIONS = 0x02, // Include LSL function calls + }; + static void setStatsFlags( S32 flags ) { sStatsFlags = flags; }; + static S32 getStatsFlags() { return sStatsFlags; }; static void clearDynamicStats(); // Reset maps to clear out dynamic objects static void addStatsToLLSDandReset( LLSD & stats, // Get current information and clear time bin @@ -213,7 +222,7 @@ private: LLStatTime * mPredefinedStat; // LLStatTime object to get data StatEntry * mDynamicStat; // StatEntryobject to get data - static BOOL sStatsEnabled; // Normally FALSE + static S32 sStatsFlags; // Control what is being recorded static stat_map_t sStatMap; // Map full path string to LLStatTime objects static std::string sCurrentStatPath; // Something like "frame/physics/physics step" }; @@ -236,7 +245,7 @@ public: BOOL frameStatsIsRunning() { return (mReportPerformanceStatEnd > 0.); }; F32 getReportPerformanceInterval() const { return mReportPerformanceStatInterval; }; void setReportPerformanceInterval( F32 interval ) { mReportPerformanceStatInterval = interval; }; - void setReportPerformanceDuration( F32 seconds ); + void setReportPerformanceDuration( F32 seconds, S32 flags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS ); void setProcessName(const std::string& process_name) { mProcessName = process_name; } void setProcessPID(S32 process_pid) { mProcessPID = process_pid; } diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 1b93c21982..900652d06d 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -178,8 +178,8 @@ LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), mMaxBytes(0), mRawDiscardLevel(-1), mRate(0.0f), - mReversible(FALSE) - + mReversible(FALSE), + mAreaUsedForDataSizeCalcs(0) { //We assume here that if we wanted to create via //a dynamic library that the approriate open calls were made @@ -195,6 +195,12 @@ LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), } mImpl = j2cimpl_create_func(); + + // Clear data size table + for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++) + { // Array size is MAX_DISCARD_LEVEL+1 + mDataSizes[i] = 0; + } } // virtual @@ -367,9 +373,45 @@ S32 LLImageJ2C::calcHeaderSize() return calcHeaderSizeJ2C(); } + +// calcDataSize() returns how many bytes to read +// to load discard_level (including header and higher discard levels) S32 LLImageJ2C::calcDataSize(S32 discard_level) { - return calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), discard_level, mRate); + discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL); + + if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth()) + || mDataSizes[0] == 0) + { + mAreaUsedForDataSizeCalcs = getHeight() * getWidth(); + + S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard + while ( level >= 0 ) + { + mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); + level--; + } + + /* This is technically a more correct way to calculate the size required + for each discard level, since they should include the size needed for + lower levels. Unfortunately, this doesn't work well and will lead to + download stalls. The true correct way is to parse the header. This will + all go away with http textures at some point. + + // Calculate the size for each discard level. Lower levels (higher quality) + // contain the cumulative size of higher levels + S32 total_size = calcHeaderSizeJ2C(); + + S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard + while ( level >= 0 ) + { // Add in this discard level and all before it + total_size += calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); + mDataSizes[level] = total_size; + level--; + } + */ + } + return mDataSizes[discard_level]; } S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index 23f6ef5fd1..55df7f4429 100644 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h @@ -87,6 +87,10 @@ protected: void updateRawDiscardLevel(); S32 mMaxBytes; // Maximum number of bytes of data to use... + + S32 mDataSizes[MAX_DISCARD_LEVEL+1]; // Size of data required to reach a given level + U32 mAreaUsedForDataSizeCalcs; // Height * width used to calculate mDataSizes + S8 mRawDiscardLevel; F32 mRate; BOOL mReversible; diff --git a/indra/llinventory/llpermissions.cpp b/indra/llinventory/llpermissions.cpp index 328ed4588b..2a27f3fc8d 100644 --- a/indra/llinventory/llpermissions.cpp +++ b/indra/llinventory/llpermissions.cpp @@ -277,6 +277,17 @@ BOOL LLPermissions::setOwnerAndGroup( return allowed; } +//Fix for DEV-33917, last owner isn't used much and has little impact on +//permissions so it's reasonably safe to do this, however, for now, +//limiting the functionality of this routine to objects which are +//group owned. +void LLPermissions::setLastOwner(const LLUUID& last_owner) +{ + if (isGroupOwned()) + mLastOwner = last_owner; +} + + // only call this if you know what you're doing // there are usually perm-bit consequences when the // ownerhsip changes diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h index 5587f8c3c8..9280629e0c 100644 --- a/indra/llinventory/llpermissions.h +++ b/indra/llinventory/llpermissions.h @@ -229,6 +229,10 @@ public: // ownerhsip changes void yesReallySetOwner(const LLUUID& owner, bool group_owned); + // Last owner doesn't have much in the way of permissions so it's + //not too dangerous to do this. + void setLastOwner(const LLUUID& last_owner); + // saves last owner, sets owner to uuid null, sets group // owned. group_id must be the group of the object (that's who it // is being deeded to) and the object must be group diff --git a/indra/llmath/llquaternion.cpp b/indra/llmath/llquaternion.cpp index cfd6183ec4..fdcc19d657 100644 --- a/indra/llmath/llquaternion.cpp +++ b/indra/llmath/llquaternion.cpp @@ -121,7 +121,7 @@ void LLQuaternion::quantize16(F32 lower, F32 upper) mQ[VZ] = z; mQ[VS] = s; - normQuat(); + normalize(); } void LLQuaternion::quantize8(F32 lower, F32 upper) @@ -131,7 +131,7 @@ void LLQuaternion::quantize8(F32 lower, F32 upper) mQ[VZ] = U8_to_F32(F32_to_U8_ROUND(mQ[VZ], lower, upper), lower, upper); mQ[VS] = U8_to_F32(F32_to_U8_ROUND(mQ[VS], lower, upper), lower, upper); - normQuat(); + normalize(); } // LLVector3 Magnitude and Normalization Functions @@ -346,7 +346,7 @@ const LLQuaternion& LLQuaternion::setQuat(const LLMatrix4 &mat) // mQ[VZ] = (F32)(cosX*cosY*sinZ - sinX*sinY*cosZ); //#endif // -// normQuat(); +// normalize(); // return (*this); } diff --git a/indra/llmath/llquaternion.h b/indra/llmath/llquaternion.h index 5db9c5be2e..0769f29f23 100644 --- a/indra/llmath/llquaternion.h +++ b/indra/llmath/llquaternion.h @@ -469,20 +469,30 @@ inline const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b) return a; } +const F32 ONE_PART_IN_A_MILLION = 0.000001f; + inline F32 LLQuaternion::normalize() { F32 mag = sqrtf(mQ[VX]*mQ[VX] + mQ[VY]*mQ[VY] + mQ[VZ]*mQ[VZ] + mQ[VS]*mQ[VS]); if (mag > FP_MAG_THRESHOLD) { - F32 oomag = 1.f/mag; - mQ[VX] *= oomag; - mQ[VY] *= oomag; - mQ[VZ] *= oomag; - mQ[VS] *= oomag; + // Floating point error can prevent some quaternions from achieving + // exact unity length. When trying to renormalize such quaternions we + // can oscillate between multiple quantized states. To prevent such + // drifts we only renomalize if the length is far enough from unity. + if (fabs(1.f - mag) > ONE_PART_IN_A_MILLION) + { + F32 oomag = 1.f/mag; + mQ[VX] *= oomag; + mQ[VY] *= oomag; + mQ[VZ] *= oomag; + mQ[VS] *= oomag; + } } else { + // we were given a very bad quaternion so we set it to identity mQ[VX] = 0.f; mQ[VY] = 0.f; mQ[VZ] = 0.f; @@ -499,11 +509,15 @@ inline F32 LLQuaternion::normQuat() if (mag > FP_MAG_THRESHOLD) { - F32 oomag = 1.f/mag; - mQ[VX] *= oomag; - mQ[VY] *= oomag; - mQ[VZ] *= oomag; - mQ[VS] *= oomag; + if (fabs(1.f - mag) > ONE_PART_IN_A_MILLION) + { + // only renormalize if length not close enough to 1.0 already + F32 oomag = 1.f/mag; + mQ[VX] *= oomag; + mQ[VY] *= oomag; + mQ[VZ] *= oomag; + mQ[VS] *= oomag; + } } else { diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 88f83ba78e..81e518cf6e 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -221,6 +221,7 @@ IF (NOT LINUX AND VIEWER) # llhttpclientadapter.cpp lltrustedmessageservice.cpp lltemplatemessagedispatcher.cpp + llregionpresenceverifier.cpp ) LL_ADD_PROJECT_UNIT_TESTS(llmessage "${llmessage_TEST_SOURCE_FILES}") @@ -228,3 +229,4 @@ IF (NOT LINUX AND VIEWER) # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") ENDIF (NOT LINUX AND VIEWER) + diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index 799bc83e20..82186fc503 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -76,11 +76,13 @@ public: LLCacheNameEntry(); public: - bool mIsGroup; - U32 mCreateTime; // unix time_t - std::string mFirstName; - std::string mLastName; - std::string mGroupName; + bool isUnknown() { return (mFirstName.empty() + || mFirstName == std::string("(???)")); }; + + bool mIsGroup; // true if this is a group ID/name + U32 mCreateTime; // unix time_t + std::string mFirstName; // Doubles as the group name + std::string mLastName; // Will be "" for groups }; LLCacheNameEntry::LLCacheNameEntry() @@ -162,7 +164,7 @@ void ReplySender::send(const LLUUID& id, mMsg->addUUIDFast(_PREHASH_ID, id); if(mCurrIsGroup) { - mMsg->addStringFast(_PREHASH_GroupName, entry.mGroupName); + mMsg->addStringFast(_PREHASH_GroupName, entry.mFirstName); } else { @@ -222,6 +224,7 @@ public: void processPendingReplies(); void sendRequest(const char* msg_name, const AskQueue& queue); bool isRequestPending(const LLUUID& id); + void makeNameRequestForID(const LLUUID& id, bool isGroup, LLHost & fromHost); // Message system callbacks. void processUUIDRequest(LLMessageSystem* msg, bool isGroup); @@ -389,6 +392,7 @@ void LLCacheName::importFile(LLFILE* fp) entry->mCreateTime = create_time; entry->mFirstName = firstname; entry->mLastName = lastname; + //llinfos << "Adding entry from file for " << entry->mFirstName << " " << entry->mLastName << ", id " << id << llendl; impl.mCache[id] = entry; count++; @@ -425,6 +429,7 @@ bool LLCacheName::importFile(std::istream& istr) entry->mCreateTime = ctime; entry->mFirstName = agent[FIRST].asString(); entry->mLastName = agent[LAST].asString(); + //llinfos << "Adding name entry from XML file for " << entry->mFirstName << " " << entry->mLastName << ", id " << id << llendl; impl.mCache[id] = entry; ++count; @@ -445,7 +450,9 @@ bool LLCacheName::importFile(std::istream& istr) LLCacheNameEntry* entry = new LLCacheNameEntry(); entry->mIsGroup = true; entry->mCreateTime = ctime; - entry->mGroupName = group[NAME].asString(); + entry->mFirstName = group[NAME].asString(); + entry->mLastName = ""; + //llinfos << "Adding group entry from XML file for " << entry->mFirstName << " " << entry->mLastName << ", id " << id << llendl; impl.mCache[id] = entry; ++count; } @@ -463,32 +470,32 @@ void LLCacheName::exportFile(std::ostream& ostr) // Only write entries for which we have valid data. LLCacheNameEntry* entry = iter->second; if(!entry - || (std::string::npos != entry->mFirstName.find('?')) - || (std::string::npos != entry->mGroupName.find('?'))) - { + || entry->isUnknown()) + { // No entry, or user or group name is unknown continue; } // store it LLUUID id = iter->first; std::string id_str = id.asString(); - if(!entry->mFirstName.empty() && !entry->mLastName.empty()) - { + if(entry->mIsGroup) + { // Save group name and ID + data[GROUPS][id_str][NAME] = entry->mFirstName; + data[GROUPS][id_str][CTIME] = (S32)entry->mCreateTime; + } + else if(!entry->mLastName.empty()) + { // Save user names and ID data[AGENTS][id_str][FIRST] = entry->mFirstName; data[AGENTS][id_str][LAST] = entry->mLastName; data[AGENTS][id_str][CTIME] = (S32)entry->mCreateTime; } - else if(entry->mIsGroup && !entry->mGroupName.empty()) - { - data[GROUPS][id_str][NAME] = entry->mGroupName; - data[GROUPS][id_str][CTIME] = (S32)entry->mCreateTime; - } } LLSDSerialize::toPrettyXML(data, ostr); } +// DO NOT CALL THIS FOR GROUP NAMES BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& last) { if(id.isNull()) @@ -511,11 +518,11 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las last.clear(); if (!impl.isRequestPending(id)) { + //llinfos << "**** adding name req for " << id << llendl; impl.mAskNameQueue.insert(id); } - return FALSE; } - + return FALSE; } BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) @@ -535,7 +542,7 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) } LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache,id); - if (entry && entry->mGroupName.empty()) + if (entry && entry->mFirstName.empty()) { // COUNTER-HACK to combat James' HACK in exportFile()... // this group name was loaded from a name cache that did not @@ -546,7 +553,7 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) if (entry) { - group = entry->mGroupName; + group = entry->mFirstName; return TRUE; } else @@ -562,7 +569,7 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) // TODO: Make the cache name callback take a SINGLE std::string, // not a separate first and last name. -void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data) +void LLCacheName::getNameFromUUID(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data) { if(id.isNull()) { @@ -573,15 +580,8 @@ void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callb LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id ); if (entry) { - // id found in map therefore we can call the callback immediately. - if (entry->mIsGroup) - { - callback(id, entry->mGroupName, "", entry->mIsGroup, user_data); - } - else - { - callback(id, entry->mFirstName, entry->mLastName, entry->mIsGroup, user_data); - } + // id found in map therefore we can call the callback immediately. mLastName will be empty for groups + callback(id, entry->mFirstName, entry->mLastName, entry->mIsGroup, user_data); } else { @@ -590,13 +590,17 @@ void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callb { if (is_group) { + //llinfos << "Group queued for " << id << llendl; impl.mAskGroupQueue.insert(id); } else { + //llinfos << "Name queued for " << id << llendl; impl.mAskNameQueue.insert(id); } } + + // There may be multiple replies for the same ID request impl.mReplyQueue.push_back(PendingReply(id, callback, user_data)); } } @@ -661,7 +665,7 @@ void LLCacheName::dump() { llinfos << iter->first << " = (group) " - << entry->mGroupName + << entry->mFirstName << " @ " << entry->mCreateTime << llendl; } @@ -715,17 +719,7 @@ void LLCacheName::Impl::processPendingReplies() if (it->mCallback) { - if (!entry->mIsGroup) - { - (it->mCallback)(it->mID, - entry->mFirstName, entry->mLastName, - FALSE, it->mData); - } - else { - (it->mCallback)(it->mID, - entry->mGroupName, "", - TRUE, it->mData); - } + (it->mCallback)(it->mID, entry->mFirstName, entry->mLastName, entry->mIsGroup, it->mData); } } @@ -768,10 +762,12 @@ void LLCacheName::Impl::sendRequest( if(start_new_message) { start_new_message = false; + //llinfos << "newMessageFast : " << msg_name << llendl; mMsg->newMessageFast(msg_name); } mMsg->nextBlockFast(_PREHASH_UUIDNameBlock); mMsg->addUUIDFast(_PREHASH_ID, (*it)); + //llinfos << " asking for ID: " << (*it) << llendl; if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock)) { @@ -837,38 +833,102 @@ void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup) { if (isGroup != entry->mIsGroup) { - llwarns << "LLCacheName - Asked for " - << (isGroup ? "group" : "user") << " name, " - << "but found " - << (entry->mIsGroup ? "group" : "user") - << ": " << id << llendl; + if (entry->isUnknown()) + { + Cache::iterator doomediter = mCache.find(id); + if (doomediter != mCache.end()) + { // Kill existing unknown entry + llwarns << "LLCacheName - Asked for " + << (isGroup ? "group" : "user") << " name, " + << "but found unknown " + << (entry->mIsGroup ? "group" : "user") + << " entry for: " << id + << ", deleting bad entry" + << llendl; + + delete entry; + entry = NULL; + mCache.erase(doomediter); + + // Request it with (hopefully) the correct type + makeNameRequestForID(id,isGroup,fromHost); + } + } + else if (isGroup) + { + llwarns << "LLCacheName - Asked for group name, but found user: " + << id + << " named " + << entry->mFirstName << " " << entry->mLastName + << llendl; + } + else + { + llwarns << "LLCacheName - Asked for user name, but found group: " + << id + << " named " + << entry->mFirstName + << llendl; + } } else { // ...it's in the cache, so send it as the reply sender.send(id, *entry, fromHost); - } - } - else - { - if (!isRequestPending(id)) - { + + /* if (isGroup) { - mAskGroupQueue.insert(id); + llinfos << "Group ID " << id + << " name " << entry->mFirstName + << " was already in cache" << llendl; } else { - mAskNameQueue.insert(id); + llinfos << "Agent ID " << id + << " name " << entry->mFirstName << " " << entry->mLastName + << " was already in cache" << llendl; } + */ } - - mReplyQueue.push_back(PendingReply(id, fromHost)); + } + else + { /* + if (isGroup) + { + llinfos << "Group ID " << id << " is not in cache" << llendl; + } + else + { + llinfos << "Agent ID " << id << " is not in cache" << llendl; + } + */ + makeNameRequestForID(id,isGroup,fromHost); } } } +void LLCacheName::Impl::makeNameRequestForID(const LLUUID& id, bool isGroup, LLHost & fromHost) +{ + if (!isRequestPending(id)) + { + if (isGroup) + { + //llinfos << "Adding group request for " << id << llendl; + mAskGroupQueue.insert(id); + } + else + { + //llinfos << "Adding name request for " << id << llendl; + mAskNameQueue.insert(id); + } + } + + // There may be multiple replys for the same ID request + mReplyQueue.push_back(PendingReply(id, fromHost)); +} + void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup) { @@ -878,35 +938,53 @@ void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup) LLUUID id; msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i); LLCacheNameEntry* entry = get_ptr_in_map(mCache, id); + bool add_new_entry_to_cache = false; if (!entry) { entry = new LLCacheNameEntry; - mCache[id] = entry; + add_new_entry_to_cache = true; } + // Remove ID from pending queue mPendingQueue.erase(id); - entry->mIsGroup = isGroup; - entry->mCreateTime = (U32)time(NULL); - if (!isGroup) - { - msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_FirstName, entry->mFirstName, i); - msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_LastName, entry->mLastName, i); + std::string first_name; + std::string last_name; + if (isGroup) + { // Group + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_GroupName, first_name, i); + LLStringFn::replace_ascii_controlchars(first_name, LL_UNKNOWN_CHAR); } else - { // is group - msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_GroupName, entry->mGroupName, i); - LLStringFn::replace_ascii_controlchars(entry->mGroupName, LL_UNKNOWN_CHAR); + { // User + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_FirstName, first_name, i); + msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_LastName, last_name, i); } - - if (!isGroup) - { - notifyObservers(id, entry->mFirstName, entry->mLastName, FALSE); + + if (!add_new_entry_to_cache && + (entry->mFirstName != first_name || + entry->mLastName != last_name || + entry->mIsGroup != isGroup)) + { // Hmmm, we already had an different entry for this ID. Let's see what happened... + llwarns << "Replacing existing entry in name cache for id " << id + << " first name was " << entry->mFirstName << ", now " << first_name + << " last name was " << entry->mLastName << ", now " << last_name + << " group flag was " << (S32) entry->mIsGroup << ", now " << (S32) isGroup + << llendl; } - else + + entry->mFirstName = first_name; + entry->mLastName = last_name; + entry->mIsGroup = isGroup; + entry->mCreateTime = (U32)time(NULL); + + if (add_new_entry_to_cache) { - notifyObservers(id, entry->mGroupName, "", TRUE); + //llinfos << "Adding entry for " << entry->mFirstName << " " << entry->mLastName << ", id " << id << llendl; + mCache[id] = entry; } + + notifyObservers(id, entry->mFirstName, entry->mLastName, isGroup); } } diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h index 2757b86a7c..bfa116ad4a 100644 --- a/indra/llmessage/llcachename.h +++ b/indra/llmessage/llcachename.h @@ -89,12 +89,8 @@ public: // If the data is currently available, may call the callback immediatly // otherwise, will request the data, and will call the callback when // available. There is no garuntee the callback will ever be called. - void get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data = NULL); + void getNameFromUUID(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data = NULL); - // LEGACY - void getName(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL) - { get(id, FALSE, callback, user_data); } - // This method needs to be called from time to time to send out // requests. void processPending(); diff --git a/indra/llmessage/llhttpclientadapter.cpp b/indra/llmessage/llhttpclientadapter.cpp index bbb56960df..5236a52164 100644 --- a/indra/llmessage/llhttpclientadapter.cpp +++ b/indra/llmessage/llhttpclientadapter.cpp @@ -1,5 +1,5 @@ /** - * @file + * @file llhttpclientadapter.cpp * @brief * * $LicenseInfo:firstyear=2009&license=viewergpl$ diff --git a/indra/llmessage/llhttpclientadapter.h b/indra/llmessage/llhttpclientadapter.h index d5f3aeaf2c..c489dca32d 100644 --- a/indra/llmessage/llhttpclientadapter.h +++ b/indra/llmessage/llhttpclientadapter.h @@ -1,5 +1,5 @@ /** - * @file + * @file llhttpclientadepter.h * @brief * * $LicenseInfo:firstyear=2008&license=viewergpl$ diff --git a/indra/llmessage/llhttpclientinterface.h b/indra/llmessage/llhttpclientinterface.h index 1f13d46447..61826cc4b4 100644 --- a/indra/llmessage/llhttpclientinterface.h +++ b/indra/llmessage/llhttpclientinterface.h @@ -1,5 +1,5 @@ /** - * @file + * @file llhttpclientinterface.h * @brief * * $LicenseInfo:firstyear=2008&license=viewergpl$ diff --git a/indra/llmessage/llmessagesenderinterface.h b/indra/llmessage/llmessagesenderinterface.h index 4082666339..d98d891563 100644 --- a/indra/llmessage/llmessagesenderinterface.h +++ b/indra/llmessage/llmessagesenderinterface.h @@ -1,5 +1,5 @@ /** - * @file + * @file llmessagesenderinterface.h * @brief * * $LicenseInfo:firstyear=2008&license=viewergpl$ diff --git a/indra/llmessage/llregionpresenceverifier.cpp b/indra/llmessage/llregionpresenceverifier.cpp index 552cf4cbdb..08c12f90da 100644 --- a/indra/llmessage/llregionpresenceverifier.cpp +++ b/indra/llmessage/llregionpresenceverifier.cpp @@ -1,5 +1,5 @@ /** - * @file + * @file llregionpresenceverifier.cpp * @brief * * $LicenseInfo:firstyear=2008&license=viewergpl$ @@ -25,11 +25,40 @@ #include "net.h" #include "message.h" +namespace boost +{ + void intrusive_ptr_add_ref(LLRegionPresenceVerifier::Response* p) + { + ++p->mReferenceCount; + } + + void intrusive_ptr_release(LLRegionPresenceVerifier::Response* p) + { + if(p && 0 == --p->mReferenceCount) + { + delete p; + } + } +}; -LLRegionPresenceVerifier::RegionResponder::RegionResponder(ResponsePtr data) : mSharedData(data) +LLRegionPresenceVerifier::Response::~Response() { } +LLRegionPresenceVerifier::RegionResponder::RegionResponder(const std::string& + uri, + ResponsePtr data, + S32 retry_count) : + mUri(uri), + mSharedData(data), + mRetryCount(retry_count) +{ +} + +//virtual +LLRegionPresenceVerifier::RegionResponder::~RegionResponder() +{ +} void LLRegionPresenceVerifier::RegionResponder::result(const LLSD& content) { @@ -42,26 +71,32 @@ void LLRegionPresenceVerifier::RegionResponder::result(const LLSD& content) std::stringstream uri; uri << "http://" << destination.getString() << "/state/basic/"; - mSharedData->getHttpClient().get(uri.str(), new VerifiedDestinationResponder(mSharedData, content)); + mSharedData->getHttpClient().get( + uri.str(), + new VerifiedDestinationResponder(mUri, mSharedData, content, mRetryCount)); } -void LLRegionPresenceVerifier::RegionResponder::completed( - U32 status, - const std::string& reason, - const LLSD& content) +void LLRegionPresenceVerifier::RegionResponder::error(U32 status, + const std::string& reason) { - LLHTTPClient::Responder::completed(status, reason, content); - - mSharedData->onCompletedRegionRequest(); + // TODO: babbage: distinguish between region presence service and + // region verification errors? + mSharedData->onRegionVerificationFailed(); } - -LLRegionPresenceVerifier::VerifiedDestinationResponder::VerifiedDestinationResponder(ResponsePtr data, const LLSD& content) : mSharedData(data), mContent(content) +LLRegionPresenceVerifier::VerifiedDestinationResponder::VerifiedDestinationResponder(const std::string& uri, ResponsePtr data, const LLSD& content, + S32 retry_count): + mUri(uri), + mSharedData(data), + mContent(content), + mRetryCount(retry_count) { } - - +//virtual +LLRegionPresenceVerifier::VerifiedDestinationResponder::~VerifiedDestinationResponder() +{ +} void LLRegionPresenceVerifier::VerifiedDestinationResponder::result(const LLSD& content) { @@ -76,13 +111,14 @@ void LLRegionPresenceVerifier::VerifiedDestinationResponder::result(const LLSD& { mSharedData->onRegionVerified(mContent); } - else if (mSharedData->shouldRetry()) + else if (mRetryCount > 0) { retry(); } else { - llwarns << "Could not correctly look up region from region presence service. Region: " << mSharedData->getRegionUri() << llendl; + llwarns << "Simulator verification failed. Region: " << mUri << llendl; + mSharedData->onRegionVerificationFailed(); } } @@ -90,13 +126,21 @@ void LLRegionPresenceVerifier::VerifiedDestinationResponder::retry() { LLSD headers; headers["Cache-Control"] = "no-cache, max-age=0"; - llinfos << "Requesting region information, get uncached for region " << mSharedData->getRegionUri() << llendl; - mSharedData->decrementRetries(); - mSharedData->getHttpClient().get(mSharedData->getRegionUri(), new RegionResponder(mSharedData), headers); + llinfos << "Requesting region information, get uncached for region " + << mUri << llendl; + --mRetryCount; + mSharedData->getHttpClient().get(mUri, new RegionResponder(mUri, mSharedData, mRetryCount), headers); } void LLRegionPresenceVerifier::VerifiedDestinationResponder::error(U32 status, const std::string& reason) { - retry(); + if(mRetryCount > 0) + { + retry(); + } + else + { + llwarns << "Failed to contact simulator for verification. Region: " << mUri << llendl; + mSharedData->onRegionVerificationFailed(); + } } - diff --git a/indra/llmessage/llregionpresenceverifier.h b/indra/llmessage/llregionpresenceverifier.h index d1de608ec6..f11eeef50c 100644 --- a/indra/llmessage/llregionpresenceverifier.h +++ b/indra/llmessage/llregionpresenceverifier.h @@ -1,5 +1,5 @@ /** - * @file + * @file llregionpresenceverifier.cpp * @brief * * $LicenseInfo:firstyear=2008&license=viewergpl$ @@ -26,7 +26,7 @@ #include "llhttpclient.h" #include #include "llsd.h" -#include +#include class LLHTTPClientInterface; @@ -36,48 +36,57 @@ public: class Response { public: - virtual ~Response() {} + virtual ~Response() = 0; virtual bool checkValidity(const LLSD& content) const = 0; virtual void onRegionVerified(const LLSD& region_details) = 0; - - virtual void decrementRetries() = 0; + virtual void onRegionVerificationFailed() = 0; virtual LLHTTPClientInterface& getHttpClient() = 0; - virtual std::string getRegionUri() const = 0; - virtual bool shouldRetry() const = 0; - virtual void onCompletedRegionRequest() {} + public: /* but not really -- don't touch this */ + U32 mReferenceCount; }; - typedef boost::shared_ptr ResponsePtr; + typedef boost::intrusive_ptr ResponsePtr; class RegionResponder : public LLHTTPClient::Responder { public: - RegionResponder(ResponsePtr data); + RegionResponder(const std::string& uri, ResponsePtr data, + S32 retry_count); + virtual ~RegionResponder(); virtual void result(const LLSD& content); - virtual void completed( - U32 status, - const std::string& reason, - const LLSD& content); + virtual void error(U32 status, const std::string& reason); private: ResponsePtr mSharedData; + std::string mUri; + S32 mRetryCount; }; class VerifiedDestinationResponder : public LLHTTPClient::Responder { public: - VerifiedDestinationResponder(ResponsePtr data, const LLSD& content); + VerifiedDestinationResponder(const std::string& uri, ResponsePtr data, + const LLSD& content, S32 retry_count); + virtual ~VerifiedDestinationResponder(); virtual void result(const LLSD& content); virtual void error(U32 status, const std::string& reason); + private: void retry(); ResponsePtr mSharedData; LLSD mContent; + std::string mUri; + S32 mRetryCount; }; }; +namespace boost +{ + void intrusive_ptr_add_ref(LLRegionPresenceVerifier::Response* p); + void intrusive_ptr_release(LLRegionPresenceVerifier::Response* p); +}; #endif //LL_LLREGIONPRESENCEVERIFIER_H diff --git a/indra/llmessage/llstoredmessage.cpp b/indra/llmessage/llstoredmessage.cpp index 615eff405d..da6d1c84a8 100644 --- a/indra/llmessage/llstoredmessage.cpp +++ b/indra/llmessage/llstoredmessage.cpp @@ -1,5 +1,5 @@ /** - * @file + * @file llstoredmessage.cpp * @brief * * $LicenseInfo:firstyear=2009&license=viewergpl$ diff --git a/indra/llmessage/llstoredmessage.h b/indra/llmessage/llstoredmessage.h index e817f19bd2..6a27698b03 100644 --- a/indra/llmessage/llstoredmessage.h +++ b/indra/llmessage/llstoredmessage.h @@ -1,5 +1,5 @@ /** - * @file + * @file llstoredmessage.h * @brief * * $LicenseInfo:firstyear=2009&license=viewergpl$ diff --git a/indra/llmessage/llthrottle.cpp b/indra/llmessage/llthrottle.cpp index 70279a3c62..0872efba50 100644 --- a/indra/llmessage/llthrottle.cpp +++ b/indra/llmessage/llthrottle.cpp @@ -265,6 +265,31 @@ BOOL LLThrottleGroup::setNominalBPS(F32* throttle_vec) return changed; } +// Return bits available in the channel +S32 LLThrottleGroup::getAvailable(S32 throttle_cat) +{ + S32 retval = 0; + + F32 category_bps = mCurrentBPS[throttle_cat]; + F32 lookahead_bits = category_bps * THROTTLE_LOOKAHEAD_TIME; + + // use a temporary bits_available + // since we don't want to change mBitsAvailable every time + F32 elapsed_time = (F32)(LLMessageSystem::getMessageTimeSeconds() - mLastSendTime[throttle_cat]); + F32 bits_available = mBitsAvailable[throttle_cat] + (category_bps * elapsed_time); + + if (bits_available >= lookahead_bits) + { + retval = (S32) gThrottleMaximumBPS[throttle_cat]; + } + else + { + retval = (S32) bits_available; + } + + return retval; +} + BOOL LLThrottleGroup::checkOverflow(S32 throttle_cat, F32 bits) { diff --git a/indra/llmessage/llthrottle.h b/indra/llmessage/llthrottle.h index 7d1679beb2..47a7c653b2 100644 --- a/indra/llmessage/llthrottle.h +++ b/indra/llmessage/llthrottle.h @@ -84,6 +84,8 @@ public: BOOL dynamicAdjust(); // Shift bandwidth from idle channels to busy channels, TRUE if adjustment occurred BOOL setNominalBPS(F32* throttle_vec); // TRUE if any value was different, resets adjustment system if was different + S32 getAvailable(S32 throttle_cat); // Return bits available in the channel + void packThrottle(LLDataPacker &dp) const; void unpackThrottle(LLDataPacker &dp); public: diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 3ab8057abb..81b7761ed5 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -51,6 +51,7 @@ static const U32 HTTP_STATUS_PIPE_ERROR = 499; * String constants */ const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri"); +const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes"); static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user); @@ -247,7 +248,29 @@ LLIOPipe::EStatus LLURLRequest::process_impl( PUMP_DEBUG; LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); //llinfos << "LLURLRequest::process_impl()" << llendl; - if(!buffer) return STATUS_ERROR; + if (!buffer) return STATUS_ERROR; + + // we're still waiting or prcessing, check how many + // bytes we have accumulated. + const S32 MIN_ACCUMULATION = 100000; + if(pump && (mDetail->mByteAccumulator > MIN_ACCUMULATION)) + { + // This is a pretty sloppy calculation, but this + // tries to make the gross assumption that if data + // is coming in at 56kb/s, then this transfer will + // probably succeed. So, if we're accumlated + // 100,000 bytes (MIN_ACCUMULATION) then let's + // give this client another 2s to complete. + const F32 TIMEOUT_ADJUSTMENT = 2.0f; + mDetail->mByteAccumulator = 0; + pump->adjustTimeoutSeconds(TIMEOUT_ADJUSTMENT); + lldebugs << "LLURLRequest adjustTimeoutSeconds for request: " << mDetail->mURL << llendl; + if (mState == STATE_INITIALIZED) + { + llinfos << "LLURLRequest adjustTimeoutSeconds called during upload" << llendl; + } + } + switch(mState) { case STATE_INITIALIZED: @@ -286,27 +309,14 @@ LLIOPipe::EStatus LLURLRequest::process_impl( bool newmsg = mDetail->mCurlRequest->getResult(&result); if(!newmsg) { - // we're still waiting or prcessing, check how many - // bytes we have accumulated. - const S32 MIN_ACCUMULATION = 100000; - if(pump && (mDetail->mByteAccumulator > MIN_ACCUMULATION)) - { - // This is a pretty sloppy calculation, but this - // tries to make the gross assumption that if data - // is coming in at 56kb/s, then this transfer will - // probably succeed. So, if we're accumlated - // 100,000 bytes (MIN_ACCUMULATION) then let's - // give this client another 2s to complete. - const F32 TIMEOUT_ADJUSTMENT = 2.0f; - mDetail->mByteAccumulator = 0; - pump->adjustTimeoutSeconds(TIMEOUT_ADJUSTMENT); - } - // keep processing break; } mState = STATE_HAVE_RESPONSE; + context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes; + context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes; + lldebugs << this << "Setting context to " << context << llendl; switch(result) { case CURLE_OK: @@ -353,10 +363,16 @@ LLIOPipe::EStatus LLURLRequest::process_impl( // we already stuffed everything into channel in in the curl // callback, so we are done. eos = true; + context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes; + context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes; + lldebugs << this << "Setting context to " << context << llendl; return STATUS_DONE; default: PUMP_DEBUG; + context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes; + context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes; + lldebugs << this << "Setting context to " << context << llendl; return STATUS_ERROR; } } @@ -369,6 +385,8 @@ void LLURLRequest::initialize() mDetail->mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1); mDetail->mCurlRequest->setWriteCallback(&downCallback, (void*)this); mDetail->mCurlRequest->setReadCallback(&upCallback, (void*)this); + mRequestTransferedBytes = 0; + mResponseTransferedBytes = 0; } bool LLURLRequest::configure() @@ -471,6 +489,7 @@ size_t LLURLRequest::downCallback( req->mDetail->mChannels.out(), (U8*)data, bytes); + req->mResponseTransferedBytes += bytes; req->mDetail->mByteAccumulator += bytes; return bytes; } @@ -494,6 +513,7 @@ size_t LLURLRequest::upCallback( req->mDetail->mLastRead, (U8*)data, bytes); + req->mRequestTransferedBytes += bytes; return bytes; } diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index 86ef71f085..cb3c466440 100644 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -45,6 +45,12 @@ #include "llchainio.h" #include "llerror.h" + +extern const std::string CONTEXT_REQUEST; +extern const std::string CONTEXT_DEST_URI_SD_LABEL; +extern const std::string CONTEXT_RESPONSE; +extern const std::string CONTEXT_TRANSFERED_BYTES; + class LLURLRequestDetail; class LLURLRequestComplete; @@ -208,6 +214,8 @@ protected: ERequestAction mAction; LLURLRequestDetail* mDetail; LLIOPipe::ptr_t mCompletionCallback; + S32 mRequestTransferedBytes; + S32 mResponseTransferedBytes; private: /** diff --git a/indra/llmessage/tests/llregionpresenceverifier_test.cpp b/indra/llmessage/tests/llregionpresenceverifier_test.cpp new file mode 100644 index 0000000000..b7602ef15c --- /dev/null +++ b/indra/llmessage/tests/llregionpresenceverifier_test.cpp @@ -0,0 +1,111 @@ +/** + * @file + * @brief + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2001-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "../test/lltut.h" +#include "llregionpresenceverifier.h" +#include "llcurl_stub.cpp" +#include "llhost.cpp" +#include "net.cpp" +#include "lltesthttpclientadapter.cpp" + +class LLTestResponse : public LLRegionPresenceVerifier::Response +{ +public: + + virtual bool checkValidity(const LLSD& content) const + { + return true; + } + + virtual void onRegionVerified(const LLSD& region_details) + { + } + + virtual void onRegionVerificationFailed() + { + } + + virtual LLHTTPClientInterface& getHttpClient() + { + return mHttpInterface; + } + + LLTestHTTPClientAdapter mHttpInterface; +}; + +namespace tut +{ + struct LLRegionPresenceVerifierData + { + LLRegionPresenceVerifierData() : + mResponse(new LLTestResponse()), + mResponder("", LLRegionPresenceVerifier::ResponsePtr(mResponse), + LLSD(), 3) + { + } + + LLTestResponse* mResponse; + LLRegionPresenceVerifier::VerifiedDestinationResponder mResponder; + }; + + typedef test_group factory; + typedef factory::object object; +} + +namespace +{ + tut::factory tf("LLRegionPresenceVerifier test"); +} + +namespace tut +{ + // Test that VerifiedDestinationResponder does retry + // on error when shouldRetry returns true. + template<> template<> + void object::test<1>() + { + mResponder.error(500, "Internal server error"); + ensure_equals(mResponse->mHttpInterface.mGetUrl.size(), 1); + } + + // Test that VerifiedDestinationResponder only retries + // on error until shouldRetry returns false. + template<> template<> + void object::test<2>() + { + mResponder.error(500, "Internal server error"); + mResponder.error(500, "Internal server error"); + mResponder.error(500, "Internal server error"); + mResponder.error(500, "Internal server error"); + ensure_equals(mResponse->mHttpInterface.mGetUrl.size(), 3); + } +} + diff --git a/indra/llprimitive/llprimtexturelist.cpp b/indra/llprimitive/llprimtexturelist.cpp index c9632ebdad..c1dde32993 100644 --- a/indra/llprimitive/llprimtexturelist.cpp +++ b/indra/llprimitive/llprimtexturelist.cpp @@ -386,8 +386,18 @@ void LLPrimTextureList::setSize(S32 new_size) mEntryList.resize(new_size); for (S32 index = current_size; index < new_size; ++index) { - LLTextureEntry* new_entry = LLPrimTextureList::newTextureEntry(); - mEntryList[index] = new_entry; + if (current_size > 0 + && mEntryList[current_size - 1]) + { + // copy the last valid entry for the new one + mEntryList[index] = mEntryList[current_size - 1]->newCopy(); + } + else + { + // no valid enries to copy, so we new one up + LLTextureEntry* new_entry = LLPrimTextureList::newTextureEntry(); + mEntryList[index] = new_entry; + } } } else if (new_size < current_size) diff --git a/indra/llvfs/llpidlock.cpp b/indra/llvfs/llpidlock.cpp index e231002a52..452594c7e7 100755 --- a/indra/llvfs/llpidlock.cpp +++ b/indra/llvfs/llpidlock.cpp @@ -61,7 +61,7 @@ class LLPidLockFile mSaving(FALSE), mWaiting(FALSE), mClean(TRUE), mPID(getpid()) { - mLockName = gDirUtilp->getTempDir() + "/savelock"; + mLockName = gDirUtilp->getTempDir() + gDirUtilp->getDirDelimiter() + "savelock"; } bool requestLock(LLNameTable *name_table, bool autosave, bool force_immediate=FALSE, F32 timeout=300.0); diff --git a/indra/lscript/lscript_byteformat.h b/indra/lscript/lscript_byteformat.h index ba2c46bef2..a54ebd5831 100644 --- a/indra/lscript/lscript_byteformat.h +++ b/indra/lscript/lscript_byteformat.h @@ -556,7 +556,7 @@ const U32 LSCRIPTRunTimePermissionBits[SCRIPT_PERMISSION_EOF] = // http_request string constants extern const char* URL_REQUEST_GRANTED; extern const char* URL_REQUEST_DENIED; -extern const U64 LSL_HTTP_REQUEST_TIMEOUT; +extern const U64 LSL_HTTP_REQUEST_TIMEOUT_USEC; #endif diff --git a/indra/lscript/lscript_compile/lscript_tree.cpp b/indra/lscript/lscript_compile/lscript_tree.cpp index e291d4c6f8..a15f1fee11 100644 --- a/indra/lscript/lscript_compile/lscript_tree.cpp +++ b/indra/lscript/lscript_compile/lscript_tree.cpp @@ -631,9 +631,7 @@ static void print_cil_cast(LLFILE* fp, LSCRIPTType srcType, LSCRIPTType targetTy switch(targetType) { case LST_INTEGER: - //fprintf(fp, "call int32 [LslLibrary]LindenLab.SecondLife.LslRunTime::ToInteger(float32)\n"); - fprintf(fp, "conv.i4\n"); // TODO replace this line with the above - // we the entire grid is > 1.25.1 + fprintf(fp, "call int32 [LslLibrary]LindenLab.SecondLife.LslRunTime::ToInteger(float32)\n"); break; case LST_STRING: fprintf(fp, "call string [LslLibrary]LindenLab.SecondLife.LslRunTime::ToString(float32)\n"); @@ -8375,10 +8373,18 @@ void LLScriptStateChange::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTComp chunk->addInteger(mIdentifier->mScopeEntry->mCount); } break; + case LSCP_TYPE: + mReturnType = basetype; + break; case LSCP_EMIT_CIL_ASSEMBLY: fprintf(fp, "ldarg.0\n"); fprintf(fp, "ldstr \"%s\"\n", mIdentifier->mName); fprintf(fp, "call instance void class [LslUserScript]LindenLab.SecondLife.LslUserScript::ChangeState(string)\n"); + // We are doing a state change. In the LSL interpreter, this is basically a longjmp. We emulate it + // here using a call to the ChangeState followed by a short cut return of the current method. To + // maintain type safety we need to push an arbitrary variable of the current method's return type + // onto the stack before returning. This will be ignored and discarded. + print_cil_init_variable(fp, mReturnType); fprintf(fp, "ret\n"); break; default: @@ -8793,7 +8799,8 @@ void LLScriptIf::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePass p } break; case LSCP_PRUNE: - prunearg = FALSE; + prunearg = TRUE; + mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); break; case LSCP_TYPE: mExpression->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -8979,7 +8986,8 @@ void LLScriptFor::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePass } break; case LSCP_PRUNE: - prunearg = FALSE; + prunearg = TRUE; + mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); break; case LSCP_TYPE: if(mSequence) @@ -9083,7 +9091,8 @@ void LLScriptDoWhile::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompileP } break; case LSCP_PRUNE: - prunearg = FALSE; + prunearg = TRUE; + mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); break; case LSCP_TYPE: mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -9159,7 +9168,8 @@ void LLScriptWhile::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePas } break; case LSCP_PRUNE: - prunearg = FALSE; + prunearg = TRUE; + mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); break; case LSCP_TYPE: mExpression->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -10127,7 +10137,10 @@ void LLScriptGlobalFunctions::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPT mStatements->recurse(fp, tabs, tabsize, pass, LSPRUNE_GLOBAL_NON_VOIDS, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); if (!prunearg) { - gErrorToText.writeError(fp, this, LSERROR_NO_RETURN); + if (!gErrorToText.getErrors()) // Hide this error when a state change has been made in a global function + { + gErrorToText.writeError(fp, this, LSERROR_NO_RETURN); + } } } else diff --git a/indra/lscript/lscript_compile/lscript_tree.h b/indra/lscript/lscript_compile/lscript_tree.h index 12c16908af..a667e1eb5b 100644 --- a/indra/lscript/lscript_compile/lscript_tree.h +++ b/indra/lscript/lscript_compile/lscript_tree.h @@ -1888,6 +1888,7 @@ public: S32 getSize(); LLScriptIdentifier *mIdentifier; + LSCRIPTType mReturnType; }; class LLScriptJump : public LLScriptStatement diff --git a/indra/lscript/lscript_execute.h b/indra/lscript/lscript_execute.h index 9a631c4c8f..8549482299 100644 --- a/indra/lscript/lscript_execute.h +++ b/indra/lscript/lscript_execute.h @@ -369,8 +369,7 @@ class LLScriptExecute { public: LLScriptExecute(); - virtual ~LLScriptExecute() {;} - + virtual ~LLScriptExecute() = 0; virtual S32 getVersion() const = 0; virtual void deleteAllEvents() = 0; virtual void addEvent(LLScriptDataCollection* event) = 0; diff --git a/indra/lscript/lscript_execute/lscript_execute.cpp b/indra/lscript/lscript_execute/lscript_execute.cpp index 05fee9a419..1f908eb675 100644 --- a/indra/lscript/lscript_execute/lscript_execute.cpp +++ b/indra/lscript/lscript_execute/lscript_execute.cpp @@ -41,6 +41,8 @@ #include "lscript_library.h" #include "lscript_heapruntime.h" #include "lscript_alloc.h" +#include "llstat.h" + // Static const S32 DEFAULT_SCRIPT_TIMER_CHECK_SKIP = 4; @@ -72,7 +74,7 @@ const char* URL_REQUEST_GRANTED = "URL_REQUEST_GRANTED"; const char* URL_REQUEST_DENIED = "URL_REQUEST_DENIED"; // HTTP Requests to LSL scripts will time out after 25 seconds. -const U64 LSL_HTTP_REQUEST_TIMEOUT = 25 * USEC_PER_SEC; +const U64 LSL_HTTP_REQUEST_TIMEOUT_USEC = 25 * USEC_PER_SEC; LLScriptExecuteLSL2::LLScriptExecuteLSL2(LLFILE *fp) { @@ -110,6 +112,7 @@ LLScriptExecuteLSL2::LLScriptExecuteLSL2(const U8* bytecode, U32 bytecode_size) init(); } +LLScriptExecute::~LLScriptExecute() {} LLScriptExecuteLSL2::~LLScriptExecuteLSL2() { delete[] mBuffer; @@ -4234,19 +4237,16 @@ S32 lscript_push_variable(LLScriptLibData *data, U8 *buffer) return 4; } -BOOL run_calllib(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) + +// Shared code for run_calllib() and run_calllib_two_byte() +BOOL run_calllib_common(U8 *buffer, S32 &offset, const LLUUID &id, U16 arg) { - if (b_print) - printf("[0x%X]\tCALLLIB ", offset); - offset++; - U8 arg = safe_instruction_bytestream2byte(buffer, offset); if (arg >= gScriptLibrary.mNextNumber) { set_fault(buffer, LSRF_BOUND_CHECK_ERROR); return FALSE; } - if (b_print) - printf("%d (%s)\n", (U32)arg, gScriptLibrary.mFunctions[arg]->mName); + LLScriptLibraryFunction *function = gScriptLibrary.mFunctions[arg]; // pull out the arguments and the return values LLScriptLibData *arguments = NULL; @@ -4254,14 +4254,14 @@ BOOL run_calllib(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) S32 i, number; - if (gScriptLibrary.mFunctions[arg]->mReturnType) + if (function->mReturnType) { returnvalue = new LLScriptLibData; } - if (gScriptLibrary.mFunctions[arg]->mArgs) + if (function->mArgs) { - number = (S32)strlen(gScriptLibrary.mFunctions[arg]->mArgs); /*Flawfinder: ignore*/ + number = (S32)strlen(function->mArgs); //Flawfinder: ignore arguments = new LLScriptLibData[number]; } else @@ -4271,24 +4271,18 @@ BOOL run_calllib(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) for (i = number - 1; i >= 0; i--) { - lscript_pop_variable(&arguments[i], buffer, gScriptLibrary.mFunctions[arg]->mArgs[i]); + lscript_pop_variable(&arguments[i], buffer, function->mArgs[i]); } - if (b_print) - { - printf("%s\n", gScriptLibrary.mFunctions[arg]->mDesc); - } + // Actually execute the function call + function->mExecFunc(returnvalue, arguments, id); - { - // LLFastTimer time_in_libraries1(LLFastTimer::FTM_TEMP7); - gScriptLibrary.mFunctions[arg]->mExecFunc(returnvalue, arguments, id); - } - add_register_fp(buffer, LREG_ESR, -gScriptLibrary.mFunctions[arg]->mEnergyUse); - add_register_fp(buffer, LREG_SLR, gScriptLibrary.mFunctions[arg]->mSleepTime); + add_register_fp(buffer, LREG_ESR, -(function->mEnergyUse)); + add_register_fp(buffer, LREG_SLR, function->mSleepTime); if (returnvalue) { - returnvalue->mType = char2type(*gScriptLibrary.mFunctions[arg]->mReturnType); + returnvalue->mType = char2type(*function->mReturnType); lscript_push_return_variable(returnvalue, buffer); } @@ -4305,72 +4299,32 @@ BOOL run_calllib(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) } -BOOL run_calllib_two_byte(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) +BOOL run_calllib(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) { - if (b_print) - printf("[0x%X]\tCALLLIB ", offset); offset++; - U16 arg = safe_instruction_bytestream2u16(buffer, offset); - if (arg >= gScriptLibrary.mNextNumber) - { - set_fault(buffer, LSRF_BOUND_CHECK_ERROR); - return FALSE; - } - if (b_print) - printf("%d (%s)\n", (U32)arg, gScriptLibrary.mFunctions[arg]->mName); - - // pull out the arguments and the return values - LLScriptLibData *arguments = NULL; - LLScriptLibData *returnvalue = NULL; - - S32 i, number; - - if (gScriptLibrary.mFunctions[arg]->mReturnType) - { - returnvalue = new LLScriptLibData; - } - - if (gScriptLibrary.mFunctions[arg]->mArgs) + U16 arg = (U16) safe_instruction_bytestream2byte(buffer, offset); + if (b_print && + arg < gScriptLibrary.mNextNumber) { - number = (S32)strlen(gScriptLibrary.mFunctions[arg]->mArgs); /*Flawfinder: ignore*/ - arguments = new LLScriptLibData[number]; - } - else - { - number = 0; - } - - for (i = number - 1; i >= 0; i--) - { - lscript_pop_variable(&arguments[i], buffer, gScriptLibrary.mFunctions[arg]->mArgs[i]); - } - - if (b_print) - { - printf("%s\n", gScriptLibrary.mFunctions[arg]->mDesc); - } - - { - // LLFastTimer time_in_libraries2(LLFastTimer::FTM_TEMP8); - gScriptLibrary.mFunctions[arg]->mExecFunc(returnvalue, arguments, id); + printf("[0x%X]\tCALLLIB ", offset); + LLScriptLibraryFunction *function = gScriptLibrary.mFunctions[arg]; + printf("%d (%s)\n", (U32)arg, function->mName); + printf("%s\n", function->mDesc); } - add_register_fp(buffer, LREG_ESR, -gScriptLibrary.mFunctions[arg]->mEnergyUse); - add_register_fp(buffer, LREG_SLR, gScriptLibrary.mFunctions[arg]->mSleepTime); + return run_calllib_common(buffer, offset, id, arg); +} - if (returnvalue) +BOOL run_calllib_two_byte(U8 *buffer, S32 &offset, BOOL b_print, const LLUUID &id) +{ + offset++; + U16 arg = safe_instruction_bytestream2u16(buffer, offset); + if (b_print && + arg < gScriptLibrary.mNextNumber) { - returnvalue->mType = char2type(*gScriptLibrary.mFunctions[arg]->mReturnType); - lscript_push_return_variable(returnvalue, buffer); + printf("[0x%X]\tCALLLIB ", (offset-1)); + LLScriptLibraryFunction *function = gScriptLibrary.mFunctions[arg]; + printf("%d (%s)\n", (U32)arg, function->mName); + printf("%s\n", function->mDesc); } - - delete [] arguments; - delete returnvalue; - - // reset the BP after calling the library files - S32 bp = lscript_pop_int(buffer); - set_bp(buffer, bp); - - // pop off the spot for the instruction pointer - lscript_poparg(buffer, 4); - return FALSE; + return run_calllib_common(buffer, offset, id, arg); } diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 06c1b2c12b..9d6c26d834 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -3063,7 +3063,7 @@ bool LLDispatchEstateUpdateInfo::operator()( regionp->setOwner(owner_id); // Update estate owner name in UI const BOOL is_group = FALSE; - gCacheName->get(owner_id, is_group, LLPanelEstateInfo::callbackCacheName); + gCacheName->getNameFromUUID(owner_id, is_group, LLPanelEstateInfo::callbackCacheName); U32 estate_id = strtoul(strings[2].c_str(), NULL, 10); panel->setEstateID(estate_id); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index a90ea39265..5b3f3952c9 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -932,7 +932,8 @@ void LLIMMgr::inviteToSession( { if (caller_name.empty()) { - gCacheName->getName(caller_id, onInviteNameLookup, new LLSD(payload)); + BOOL is_group = FALSE; // Inviter must be a person + gCacheName->getNameFromUUID(caller_id, is_group, onInviteNameLookup, new LLSD(payload)); } else { diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 40b1c64146..a840b59140 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -566,7 +566,7 @@ BOOL LLMuteList::autoRemove(const LLUUID& agent_id, const EAutoReason reason, co else { // not in cache, lookup name from cache - gCacheName->get(agent_id, FALSE, notify_automute_callback, (void *)reason); + gCacheName->getNameFromUUID(agent_id, FALSE, notify_automute_callback, (void *)reason); } } else diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index bbfd480e0b..7664d7ee07 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -1040,7 +1040,7 @@ bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD& // * we can't build two messages at once. if (2 == button) { - gCacheName->get(mFromID, mFromGroup, inventory_offer_mute_callback, this); + gCacheName->getNameFromUUID(mFromID, mFromGroup, inventory_offer_mute_callback, this); } LLMessageSystem* msg = gMessageSystem; @@ -4428,7 +4428,7 @@ void process_mean_collision_alert_message(LLMessageSystem *msgsystem, void **use LLMeanCollisionData *mcd = new LLMeanCollisionData(gAgentID, perp, time, type, mag); gMeanCollisionList.push_front(mcd); const BOOL is_group = FALSE; - gCacheName->get(perp, is_group, mean_name_callback); + gCacheName->getNameFromUUID(perp, is_group, mean_name_callback); } } } @@ -5375,7 +5375,7 @@ void process_load_url(LLMessageSystem* msg, void**) // Add to list of pending name lookups gLoadUrlList.push_back(payload); - gCacheName->get(owner_id, owner_is_group, callback_load_url_name); + gCacheName->getNameFromUUID(owner_id, owner_is_group, callback_load_url_name); } @@ -5469,7 +5469,9 @@ void process_covenant_reply(LLMessageSystem* msg, void**) LLPanelLandCovenant::updateLastModified(last_modified); LLFloaterBuyLand::updateLastModified(last_modified); - gCacheName->getName(estate_owner_id, callbackCacheEstateOwnerName); + // Estates can't be owned by groups + BOOL is_group = FALSE; + gCacheName->getNameFromUUID(estate_owner_id, is_group, callbackCacheEstateOwnerName); // load the actual covenant asset data const BOOL high_priority = TRUE; diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 94407ed08c..232885b3f6 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -6901,7 +6901,8 @@ void LLVoiceClient::notifyFriendObservers() void LLVoiceClient::lookupName(const LLUUID &id) { - gCacheName->getName(id, onAvatarNameLookup); + BOOL is_group = FALSE; + gCacheName->getNameFromUUID(id, is_group, onAvatarNameLookup); } //static -- cgit v1.3 From 82eb18fce5c3d21a49a299ee5e454cbcc9d6f559 Mon Sep 17 00:00:00 2001 From: "palmer@945battery-guestB-224.lindenlab.com" Date: Fri, 10 Jul 2009 11:09:54 -0700 Subject: Expanded tabs in cmake files --- indra/cmake/LLAddBuildTest.cmake | 12 ++++++------ indra/cmake/Linking.cmake | 2 +- indra/llcommon/CMakeLists.txt | 8 ++++---- indra/llmessage/CMakeLists.txt | 2 +- indra/newview/CMakeLists.txt | 16 ++++++++-------- 5 files changed, 20 insertions(+), 20 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 5717bc620a..0c6ddef50f 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -118,12 +118,12 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) IF(LL_TEST_VERBOSE) MESSAGE(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_cmd = ${TEST_CMD}") ENDIF(LL_TEST_VERBOSE) - - IF(WINDOWS) - set(LD_LIBRARY_PATH ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}) - ELSE(WINDOWS) - set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:/usr/lib) - ENDIF(WINDOWS) + + IF(WINDOWS) + set(LD_LIBRARY_PATH ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}) + ELSE(WINDOWS) + set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:/usr/lib) + ENDIF(WINDOWS) SET(TEST_SCRIPT_CMD ${CMAKE_COMMAND} diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index ad52a82b20..4063034837 100644 --- a/indra/cmake/Linking.cmake +++ b/indra/cmake/Linking.cmake @@ -5,7 +5,7 @@ if (NOT STANDALONE) set(ARCH_PREBUILT_DIRS ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/lib) set(ARCH_PREBUILT_DIRS_RELEASE ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/lib/release) set(ARCH_PREBUILT_DIRS_DEBUG ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/lib/debug) - set(SHARED_LIB_STAGING_DIR ${CMAKE_BINARY_DIR}/sharedlibs) + set(SHARED_LIB_STAGING_DIR ${CMAKE_BINARY_DIR}/sharedlibs) elseif (LINUX) if (VIEWER) set(ARCH_PREBUILT_DIRS ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/lib_release_client) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index aaeb4eebc3..cb0f6add21 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -238,10 +238,10 @@ list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) if(LLCOMMON_LINK_SHARED) add_library (llcommon SHARED ${llcommon_SOURCE_FILES}) add_definitions(-DLL_COMMON_BUILD=1) - # *FIX:Mani --- - # llcommon.dll get written to the DLL staging directory. - # Also this directory is shared with RunBuildTest.cmake, y'know, for the tests. - set_target_properties(llcommon PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${SHARED_LIB_STAGING_DIR}) + # *FIX:Mani --- + # llcommon.dll get written to the DLL staging directory. + # Also this directory is shared with RunBuildTest.cmake, y'know, for the tests. + set_target_properties(llcommon PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${SHARED_LIB_STAGING_DIR}) else(LLCOMMON_LINK_SHARED) add_library (llcommon ${llcommon_SOURCE_FILES}) endif(LLCOMMON_LINK_SHARED) diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 67a171dde6..2c50a9b1cd 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -226,7 +226,7 @@ IF (NOT LINUX AND VIEWER) # Commented out - see rationale at bottom of newview's build file + poppy 2009-06-05 # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! # ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py") - # llareslistener.cpp + # llareslistener.cpp ) LL_ADD_PROJECT_UNIT_TESTS(llmessage "${llmessage_TEST_SOURCE_FILES}") diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d6108ab06e..aece574eed 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1248,11 +1248,11 @@ if (WINDOWS) COMMENT "Copying message.xml to the runtime folder." ) - if(WINDOWS) + if(WINDOWS) # Copy Win Libs... - # This happens at build time, not config time. We can't glob files in this cmake. - # *FIX:Mani Write a sub script to glob the files... - add_custom_command( + # This happens at build time, not config time. We can't glob files in this cmake. + # *FIX:Mani Write a sub script to glob the files... + add_custom_command( TARGET ${VIEWER_BINARY_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} ARGS @@ -1261,10 +1261,10 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} COMMENT "Copying staged dlls." - ) - endif(WINDOWS) - - + ) + endif(WINDOWS) + + if (EXISTS ${CMAKE_SOURCE_DIR}/copy_win_scripts) add_dependencies(${VIEWER_BINARY_NAME} copy_win_scripts) -- cgit v1.3 From c2e425e5395c1b1b43a2f477565e374d919f10ee Mon Sep 17 00:00:00 2001 From: Dave Hiller Date: Tue, 14 Jul 2009 16:52:40 +0000 Subject: QAR-1651 svn merge -r126784:126785 svn+ssh://svn.lindenlab.com/svn/linden/branches/mock-3 into trunk --- indra/cmake/GoogleMock.cmake | 27 ++++++++++++++ indra/cmake/LLAddBuildTest.cmake | 11 +++++- indra/llmessage/tests/llmockhttpclient.h | 61 ++++++++++++++++++++++++++++++++ indra/test/CMakeLists.txt | 17 ++++++--- indra/test/test.cpp | 6 ++++ install.xml | 33 +++++++++++++++++ 6 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 indra/cmake/GoogleMock.cmake create mode 100644 indra/llmessage/tests/llmockhttpclient.h (limited to 'indra/llmessage') diff --git a/indra/cmake/GoogleMock.cmake b/indra/cmake/GoogleMock.cmake new file mode 100644 index 0000000000..ca5a8034ba --- /dev/null +++ b/indra/cmake/GoogleMock.cmake @@ -0,0 +1,27 @@ +# -*- cmake -*- +include(Prebuilt) +include(Linking) + +use_prebuilt_binary(googlemock) + +set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include) + +if (LINUX) + set(GOOGLEMOCK_LIBRARIES + gmock + gtest) +elseif(WINDOWS) + set(GOOGLEMOCK_LIBRARIES + gmock) + set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include + ${LIBS_PREBUILT_DIR}/include/gmock + ${LIBS_PREBUILT_DIR}/include/gmock/boost/tr1/tr1) +elseif(DARWIN) + set(GOOGLEMOCK_LIBRARIES + gmock + gtest) +endif(LINUX) + + diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 4a61725e09..4da0824120 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -13,6 +13,8 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) # # WARNING: do NOT modify this code without working with poppy or daveh - # there is another branch that will conflict heavily with any changes here. +INCLUDE(GoogleMock) + IF(LL_TEST_VERBOSE) MESSAGE("LL_ADD_PROJECT_UNIT_TESTS UNITTEST_PROJECT_${project} sources: ${sources}") @@ -32,8 +34,10 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) ${LLMATH_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LIBS_OPEN_DIR}/test + ${GOOGLEMOCK_INCLUDE_DIRS} ) SET(alltest_LIBRARIES + ${GOOGLEMOCK_LIBRARIES} ${PTHREAD_LIBRARY} ${WINDOWS_LIBRARIES} ) @@ -42,6 +46,11 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) ${CMAKE_SOURCE_DIR}/test/test.h ) + # Use the default flags + if (LINUX) + SET(CMAKE_EXE_LINKER_FLAGS "") + endif (LINUX) + # start the source test executable definitions SET(${project}_TEST_OUTPUT "") FOREACH (source ${sources}) @@ -84,9 +93,9 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) MESSAGE("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_INCLUDE_DIRS ${${name}_test_additional_INCLUDE_DIRS}") ENDIF(LL_TEST_VERBOSE) + # Setup target ADD_EXECUTABLE(PROJECT_${project}_TEST_${name} ${${name}_test_SOURCE_FILES}) - # # Per-codefile additional / external project dep and lib dep property extraction # diff --git a/indra/llmessage/tests/llmockhttpclient.h b/indra/llmessage/tests/llmockhttpclient.h new file mode 100644 index 0000000000..0de8246446 --- /dev/null +++ b/indra/llmessage/tests/llmockhttpclient.h @@ -0,0 +1,61 @@ +/** + * @file + * @brief + * + * $LicenseInfo:firstyear=2008&license=internal$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of + * this source code is governed by the Linden Lab Source Code Disclosure + * Agreement ("Agreement") previously entered between you and Linden + * Lab. By accessing, using, copying, modifying or distributing this + * software, you acknowledge that you have been informed of your + * obligations under the Agreement and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +/* Macro Definitions */ +#ifndef LL_LLMOCKHTTPCLIENT_H +#define LL_LLMOCKHTTPCLIENT_H + +#include "linden_common.h" +#include "llhttpclientinterface.h" + +#include + +class LLMockHTTPClient : public LLHTTPClientInterface +{ +public: + MOCK_METHOD2(get, void(const std::string& url, LLCurl::ResponderPtr responder)); + MOCK_METHOD3(get, void(const std::string& url, LLCurl::ResponderPtr responder, const LLSD& headers)); + MOCK_METHOD3(put, void(const std::string& url, const LLSD& body, LLCurl::ResponderPtr responder)); +}; + +// A helper to match responder types +template +struct ResponderType +{ + bool operator()(LLCurl::ResponderPtr ptr) const + { + T* p = dynamic_cast(ptr.get()); + return p != NULL; + } +}; + +inline bool operator==(const LLSD& l, const LLSD& r) +{ + std::ostringstream ls, rs; + ls << l; + rs << r; + return ls.str() == rs.str(); + +} + + +#endif //LL_LLMOCKHTTPCLIENT_H + diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 49a0a8f361..53109ca196 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -14,6 +14,7 @@ include(LScript) include(Linking) include(Tut) include(Boost) +include(GoogleMock) include_directories( ${LLCOMMON_INCLUDE_DIRS} @@ -24,6 +25,7 @@ include_directories( ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS} + ${GOOGLEMOCK_INCLUDE_DIRS} ) set(test_SOURCE_FILES @@ -121,6 +123,7 @@ target_link_libraries(test ${LLXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} ${APRICONV_LIBRARIES} ${PTHREAD_LIBRARY} ${WINDOWS_LIBRARIES} @@ -137,12 +140,18 @@ endif (WINDOWS) get_target_property(TEST_EXE test LOCATION) +SET(TEST_CMD ${TEST_EXE} --touch=${TEST_OUTPUT} --sourcedir=${CMAKE_CURRENT_SOURCE_DIR}) + +SET(TEST_LD_CMD + ${CMAKE_COMMAND} + -DLD_LIBRARY_PATH=${ARCH_PREBUILT_DIRS}:/usr/lib + -DTEST_CMD:STRING="${TEST_CMD}" + -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake + ) + add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt - COMMAND ${TEST_EXE} - ARGS - --output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt - --touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt + COMMAND ${TEST_LD_CMD} DEPENDS test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "C++ unit tests" diff --git a/indra/test/test.cpp b/indra/test/test.cpp index ba81c6e49e..c9e985c914 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -54,6 +54,9 @@ # include "ctype_workaround.h" #endif +#include +#include + namespace tut { std::string sSourceDir; @@ -238,6 +241,9 @@ void wouldHaveCrashed(const std::string& message) int main(int argc, char **argv) { + // The following line must be executed to initialize Google Mock + // (and Google Test) before running the tests. + ::testing::InitGoogleMock(&argc, argv); LLError::initForApplication("."); LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(LLError::LEVEL_ERROR); diff --git a/install.xml b/install.xml index a0d53dbcc0..464df8f710 100644 --- a/install.xml +++ b/install.xml @@ -541,6 +541,39 @@ + googlemock + + copyright + Copyright 2008, Google Inc. + description + Google C++ Mocking Framework (or Google Mock for short) is a library for writing and using C++ mock classes. + license + bsd + packages + + darwin + + md5sum + 4863e9fea433d0a4be761ea5d3e8346a + url + scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-darwin-20090626.tar.bz2 + + linux + + md5sum + 877dabecf84339690191c6115c76366e + url + scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-linux32-20090527.tar.bz2 + + windows + + md5sum + be37695d9f26552aec81c8e97ded0212 + url + scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-windows-20090529.tar.bz2 + + + gstreamer license -- cgit v1.3 From d025b721e0d39f53ef796f9c9e28e94b6d1cea20 Mon Sep 17 00:00:00 2001 From: Christian Goetze Date: Wed, 15 Jul 2009 17:55:58 +0000 Subject: svn merge -r126867:127217 svn+ssh://svn.lindenlab.com/svn/user/cg/qar-1691 Effective merge: svn merge -r125827:127126 svn+ssh://svn.lindenlab.com/svn/linden/branches/server/server-1.27 QAR-1691 --- indra/llmessage/lliohttpserver.cpp | 14 +++++++--- indra/llmessage/llxfermanager.cpp | 36 +++++++++++++++----------- indra/llprimitive/llprimtexturelist.cpp | 5 ++-- indra/lscript/lscript_compile/lscript_tree.cpp | 17 ++++-------- 4 files changed, 38 insertions(+), 34 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llmessage/lliohttpserver.cpp b/indra/llmessage/lliohttpserver.cpp index 83dfa94f00..ce815cc85b 100644 --- a/indra/llmessage/lliohttpserver.cpp +++ b/indra/llmessage/lliohttpserver.cpp @@ -520,7 +520,7 @@ protected: * seek orfor string assignment. * @returns Returns true if a line was found. */ - bool readLine( + bool readHeaderLine( const LLChannelDescriptors& channels, buffer_ptr_t buffer, U8* dest, @@ -591,7 +591,7 @@ LLHTTPResponder::~LLHTTPResponder() //lldebugs << "destroying LLHTTPResponder" << llendl; } -bool LLHTTPResponder::readLine( +bool LLHTTPResponder::readHeaderLine( const LLChannelDescriptors& channels, buffer_ptr_t buffer, U8* dest, @@ -669,7 +669,7 @@ LLIOPipe::EStatus LLHTTPResponder::process_impl( #endif PUMP_DEBUG; - if(readLine(channels, buffer, (U8*)buf, len)) + if(readHeaderLine(channels, buffer, (U8*)buf, len)) { bool read_next_line = false; bool parse_all = true; @@ -733,7 +733,13 @@ LLIOPipe::EStatus LLHTTPResponder::process_impl( if(read_next_line) { len = HEADER_BUFFER_SIZE; - readLine(channels, buffer, (U8*)buf, len); + if (!readHeaderLine(channels, buffer, (U8*)buf, len)) + { + // Failed to read the header line, probably too long. + // readHeaderLine already marked the channel/buffer as bad. + keep_parsing = false; + break; + } } if(0 == len) { diff --git a/indra/llmessage/llxfermanager.cpp b/indra/llmessage/llxfermanager.cpp index 08c9192c9f..209bdb2249 100644 --- a/indra/llmessage/llxfermanager.cpp +++ b/indra/llmessage/llxfermanager.cpp @@ -760,30 +760,36 @@ static bool remove_prefix(std::string& filename, const std::string& prefix) static bool verify_cache_filename(const std::string& filename) { //NOTE: This routine is only used to check file names that our own - // code places in the cache directory. As such, it can be limited - // to this very restrictive file name pattern. It does not need to - // handle other characters. - + // code places in the cache directory. As such, it can be limited + // to this very restrictive file name pattern. It does not need to + // handle other characters. The only known uses of this are (with examples): + // sim to sim object pass: fc0b72d8-9456-63d9-a802-a557ef847313.tmp + // sim to viewer mute list: mute_b78eacd0-1244-448e-93ca-28ede242f647.tmp + // sim to viewer task inventory: inventory_d8ab59d2-baf0-0e79-c4c2-a3f99b9fcf45.tmp + + //IMPORTANT: Do not broaden the filenames accepted by this routine + // without careful analysis. Anything allowed by this function can + // be downloaded by the viewer. + size_t len = filename.size(); - //const boost::regex expr("[a-zA-Z0-9][-_.a-zA-Z0-9]<0,49>"); - if (len < 1 || len > 50) - { + //const boost::regex expr("[0-9a-zA-Z_-]<1,46>\.tmp"); + if (len < 5 || len > 50) + { return false; } - for(unsigned i=0; i 0) - { - ok = '_'==c || '-'==c || '.'==c; - } + bool ok = isalnum(c) || '_'==c || '-'==c; if (!ok) { return false; } } - return true; + return filename[len-4] == '.' + && filename[len-3] == 't' + && filename[len-2] == 'm' + && filename[len-1] == 'p'; } void LLXferManager::processFileRequest (LLMessageSystem *mesgsys, void ** /*user_data*/) diff --git a/indra/llprimitive/llprimtexturelist.cpp b/indra/llprimitive/llprimtexturelist.cpp index c1dde32993..b02d4c50bd 100644 --- a/indra/llprimitive/llprimtexturelist.cpp +++ b/indra/llprimitive/llprimtexturelist.cpp @@ -134,13 +134,12 @@ S32 LLPrimTextureList::copyTexture(const U8 index, const LLTextureEntry& te) { if (S32(index) >= mEntryList.size()) { - // TODO -- assert here S32 current_size = mEntryList.size(); - llerrs << "index = " << S32(index) << " current_size = " << current_size << llendl; + llwarns << "ignore copy of index = " << S32(index) << " into texture entry list of size = " << current_size << llendl; return TEM_CHANGE_NONE; } - // we're changing an existing entry + // we're changing an existing entry llassert(mEntryList[index]); delete (mEntryList[index]); if (&te) diff --git a/indra/lscript/lscript_compile/lscript_tree.cpp b/indra/lscript/lscript_compile/lscript_tree.cpp index a15f1fee11..7fa115bb20 100644 --- a/indra/lscript/lscript_compile/lscript_tree.cpp +++ b/indra/lscript/lscript_compile/lscript_tree.cpp @@ -8799,8 +8799,7 @@ void LLScriptIf::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePass p } break; case LSCP_PRUNE: - prunearg = TRUE; - mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); + prunearg = FALSE; break; case LSCP_TYPE: mExpression->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -8986,8 +8985,7 @@ void LLScriptFor::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePass } break; case LSCP_PRUNE: - prunearg = TRUE; - mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); + prunearg = FALSE; break; case LSCP_TYPE: if(mSequence) @@ -9091,8 +9089,7 @@ void LLScriptDoWhile::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompileP } break; case LSCP_PRUNE: - prunearg = TRUE; - mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); + prunearg = FALSE; break; case LSCP_TYPE: mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -9168,8 +9165,7 @@ void LLScriptWhile::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPTCompilePas } break; case LSCP_PRUNE: - prunearg = TRUE; - mStatement->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); + prunearg = FALSE; break; case LSCP_TYPE: mExpression->recurse(fp, tabs, tabsize, pass, ptype, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); @@ -10137,10 +10133,7 @@ void LLScriptGlobalFunctions::recurse(LLFILE *fp, S32 tabs, S32 tabsize, LSCRIPT mStatements->recurse(fp, tabs, tabsize, pass, LSPRUNE_GLOBAL_NON_VOIDS, prunearg, scope, type, basetype, count, chunk, heap, stacksize, entry, entrycount, NULL); if (!prunearg) { - if (!gErrorToText.getErrors()) // Hide this error when a state change has been made in a global function - { - gErrorToText.writeError(fp, this, LSERROR_NO_RETURN); - } + gErrorToText.writeError(fp, this, LSERROR_NO_RETURN); } } else -- cgit v1.3 From c15b6d7d40ac03957b417cbe0e67d9c1bfa7703f Mon Sep 17 00:00:00 2001 From: Christian Goetze Date: Thu, 16 Jul 2009 20:00:21 +0000 Subject: Make googlemock publicly available --- indra/llmessage/tests/llmockhttpclient.h | 2 +- install.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llmessage/tests/llmockhttpclient.h b/indra/llmessage/tests/llmockhttpclient.h index 0de8246446..2f55e97fcc 100644 --- a/indra/llmessage/tests/llmockhttpclient.h +++ b/indra/llmessage/tests/llmockhttpclient.h @@ -2,7 +2,7 @@ * @file * @brief * - * $LicenseInfo:firstyear=2008&license=internal$ + * $LicenseInfo:firstyear=2008&license=viewergpl$ * * Copyright (c) 2008, Linden Research, Inc. * diff --git a/install.xml b/install.xml index 464df8f710..b980028447 100644 --- a/install.xml +++ b/install.xml @@ -556,21 +556,21 @@ md5sum 4863e9fea433d0a4be761ea5d3e8346a url - scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-darwin-20090626.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/googlemock-1.1.0-darwin-20090626.tar.bz2 linux md5sum 877dabecf84339690191c6115c76366e url - scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-linux32-20090527.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/googlemock-1.1.0-linux32-20090527.tar.bz2 windows md5sum be37695d9f26552aec81c8e97ded0212 url - scp:install-packages.lindenlab.com:/local/www/install-packages/doc/googlemock-1.1.0-windows-20090529.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/googlemock-1.1.0-windows-20090529.tar.bz2
-- cgit v1.3 From 17b9cda4325a035f00e077a6a8e33a8c4f2d5a89 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Fri, 24 Jul 2009 00:46:26 +0000 Subject: For QAR-1710 : Server merge for QAR-1594 QAR-1643 QAR-1644 - "AVP Changes [SIM]" svn merge -r 128022:128028 svn+ssh://svn.lindenlab.com/svn/linden/branches/avatar-pipeline/server__merge__trunk-r127980 to svn+ssh://svn.lindenlab.com/svn/linden/trunk This is the server-side merge for inventory links, folder links&types, and landmark&callingcard permissions. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llassettype.cpp | 340 ++++++++++++++++-------------- indra/llcommon/llassettype.h | 170 ++++++++------- indra/llcommon/lldictionary.h | 106 ++++++++++ indra/llcommon/stdenums.h | 3 +- indra/llinventory/llinventory.cpp | 54 ++++- indra/llinventory/llinventory.h | 26 ++- indra/llinventory/llinventorytype.cpp | 241 ++++++++++----------- indra/llinventory/llinventorytype.h | 10 +- indra/llinventory/llparcel.h | 4 +- indra/llinventory/llpermissions.cpp | 11 + indra/llinventory/llpermissions.h | 3 + indra/llmessage/lltransfersourceasset.cpp | 48 +++-- indra/llmessage/message_prehash.cpp | 1 + indra/llmessage/message_prehash.h | 1 + indra/newview/llinventorybridge.cpp | 9 +- indra/newview/llviewerinventory.cpp | 4 +- scripts/messages/message_template.msg | 18 ++ 18 files changed, 653 insertions(+), 397 deletions(-) create mode 100644 indra/llcommon/lldictionary.h (limited to 'indra/llmessage') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index beac8df636..7bfcd43684 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -99,6 +99,7 @@ set(llcommon_HEADER_FILES lldefs.h lldependencies.h lldepthstack.h + lldictionary.h lldlinked.h lldqueueptr.h llendianswizzle.h diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index cf3bf89b4f..b852e4c00f 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -33,145 +33,125 @@ #include "linden_common.h" #include "llassettype.h" +#include "lldictionary.h" +#include "llmemory.h" -#include "llstring.h" -#include "lltimer.h" +///---------------------------------------------------------------------------- +/// Class LLAssetType +///---------------------------------------------------------------------------- +struct AssetEntry : public LLDictionaryEntry +{ + AssetEntry(const char *desc_name, + const char *type_name, // 8 character limit! + const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one + const char *category_name, // used by llinventorymodel when creating new categories + EDragAndDropType dad_type, + bool can_link, // can you create a link to this type? + bool is_protected) // can the viewer change categories of this type? + : + LLDictionaryEntry(desc_name), + mTypeName(type_name), + mHumanName(human_name), + mCategoryName(category_name), + mDadType(dad_type), + mCanLink(can_link), + mIsProtected(is_protected) + { + llassert(strlen(mTypeName) <= 8); + } -// I added lookups for exact text of asset type enums in addition to the ones below, so shoot me. -Steve + const char *mTypeName; + const char *mHumanName; + const char *mCategoryName; + EDragAndDropType mDadType; + bool mCanLink; + bool mIsProtected; +}; -struct asset_info_t +class LLAssetDictionary : public LLSingleton, + public LLDictionary { - LLAssetType::EType type; - const char* desc; +public: + LLAssetDictionary(); }; -asset_info_t asset_types[] = +LLAssetDictionary::LLAssetDictionary() { - { LLAssetType::AT_TEXTURE, "TEXTURE" }, - { LLAssetType::AT_SOUND, "SOUND" }, - { LLAssetType::AT_CALLINGCARD, "CALLINGCARD" }, - { LLAssetType::AT_LANDMARK, "LANDMARK" }, - { LLAssetType::AT_SCRIPT, "SCRIPT" }, - { LLAssetType::AT_CLOTHING, "CLOTHING" }, - { LLAssetType::AT_OBJECT, "OBJECT" }, - { LLAssetType::AT_NOTECARD, "NOTECARD" }, - { LLAssetType::AT_CATEGORY, "CATEGORY" }, - { LLAssetType::AT_ROOT_CATEGORY, "ROOT_CATEGORY" }, - { LLAssetType::AT_LSL_TEXT, "LSL_TEXT" }, - { LLAssetType::AT_LSL_BYTECODE, "LSL_BYTECODE" }, - { LLAssetType::AT_TEXTURE_TGA, "TEXTURE_TGA" }, - { LLAssetType::AT_BODYPART, "BODYPART" }, - { LLAssetType::AT_TRASH, "TRASH" }, - { LLAssetType::AT_SNAPSHOT_CATEGORY, "SNAPSHOT_CATEGORY" }, - { LLAssetType::AT_LOST_AND_FOUND, "LOST_AND_FOUND" }, - { LLAssetType::AT_SOUND_WAV, "SOUND_WAV" }, - { LLAssetType::AT_IMAGE_TGA, "IMAGE_TGA" }, - { LLAssetType::AT_IMAGE_JPEG, "IMAGE_JPEG" }, - { LLAssetType::AT_ANIMATION, "ANIMATION" }, - { LLAssetType::AT_GESTURE, "GESTURE" }, - { LLAssetType::AT_SIMSTATE, "SIMSTATE" }, - { LLAssetType::AT_NONE, "NONE" }, + // DESCRIPTION TYPE NAME HUMAN NAME CATEGORY NAME DRAG&DROP CAN LINK? PROTECTED? + // |--------------------|-----------|-------------------|-------------------|---------------|-----------|-----------| + addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", "Textures", DAD_TEXTURE, FALSE, TRUE)); + addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", "Sounds", DAD_SOUND, FALSE, TRUE)); + addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", "Calling Cards", DAD_CALLINGCARD, FALSE, TRUE)); + addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", "Landmarks", DAD_LANDMARK, FALSE, TRUE)); + addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", "Scripts", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", "Clothing", DAD_CLOTHING, TRUE, TRUE)); + addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", "Objects", DAD_OBJECT, TRUE, TRUE)); + addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", "Notecards", DAD_NOTECARD, FALSE, TRUE)); + addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", "New Folder", DAD_CATEGORY, TRUE, TRUE)); + addEntry(LLAssetType::AT_ROOT_CATEGORY, new AssetEntry("ROOT_CATEGORY", "root", "root", "Inventory", DAD_ROOT_CATEGORY, TRUE, TRUE)); + addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", "Scripts", DAD_SCRIPT, FALSE, TRUE)); + addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", "Scripts", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", "Uncompressed Images", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", "Body Parts", DAD_BODYPART, TRUE, TRUE)); + addEntry(LLAssetType::AT_TRASH, new AssetEntry("TRASH", "trash", "trash", "Trash", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_SNAPSHOT_CATEGORY, new AssetEntry("SNAPSHOT_CATEGORY", "snapshot", "snapshot", "Photo Album", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_LOST_AND_FOUND, new AssetEntry("LOST_AND_FOUND", "lstndfnd", "lost and found", "Lost And Found", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", "Uncompressed SoundS", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", "Uncompressed Images", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", "Uncompressed Images", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", "Animations", DAD_ANIMATION, FALSE, TRUE)); + addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", "Gestures", DAD_GESTURE, FALSE, TRUE)); + addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", "New Folder", DAD_NONE, FALSE, TRUE)); + addEntry(LLAssetType::AT_FAVORITE, new AssetEntry("FAVORITE", "favorite", "favorite", "favorite", DAD_NONE, FALSE, TRUE)); + + addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "symbolic link", "Link", DAD_LINK, FALSE, TRUE)); + addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "symbolic folder link", "New Folder", DAD_LINK, FALSE, TRUE)); + + for (S32 ensemble_num = S32(LLAssetType::AT_FOLDER_ENSEMBLE_START); + ensemble_num <= S32(LLAssetType::AT_FOLDER_ENSEMBLE_END); + ensemble_num++) + { + addEntry(LLAssetType::EType(ensemble_num), new AssetEntry("ENSEMBLE", "ensemble", "ensemble", "New Folder", DAD_CATEGORY, TRUE, FALSE)); + } + + addEntry(LLAssetType::AT_CURRENT_OUTFIT, new AssetEntry("CURRENT", "current", "current outfit", "Current Outfit", DAD_CATEGORY, FALSE, TRUE)); + addEntry(LLAssetType::AT_OUTFIT, new AssetEntry("OUTFIT", "outfit", "outfit", "Outfit", DAD_CATEGORY, TRUE, FALSE)); + addEntry(LLAssetType::AT_MY_OUTFITS, new AssetEntry("MY_OUTFITS", "my_otfts", "my outfits", "My Outfits", DAD_CATEGORY, FALSE, TRUE)); + + addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, "New Folder", DAD_NONE, FALSE, FALSE)); }; -LLAssetType::EType LLAssetType::getType(const std::string& sin) +// static +LLAssetType::EType LLAssetType::getType(const std::string& desc_name) { - std::string s = sin; + std::string s = desc_name; LLStringUtil::toUpper(s); - for (S32 idx = 0; ;idx++) - { - asset_info_t* info = asset_types + idx; - if (info->type == LLAssetType::AT_NONE) - break; - if (s == info->desc) - return info->type; - } - return LLAssetType::AT_NONE; + return LLAssetDictionary::getInstance()->lookup(s); } -std::string LLAssetType::getDesc(LLAssetType::EType type) +// static +const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) { - for (S32 idx = 0; ;idx++) + const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type); + if (entry) + { + return entry->mName; + } + else { - asset_info_t* info = asset_types + idx; - if (type == info->type) - return info->desc; - if (info->type == LLAssetType::AT_NONE) - break; + static const std::string error_string = "BAD TYPE"; + return error_string; } - return "BAD TYPE"; } -//============================================================================ - -// The asset type names are limited to 8 characters. -// static -const char* LLAssetType::mAssetTypeNames[LLAssetType::AT_COUNT] = -{ - "texture", - "sound", - "callcard", - "landmark", - "script", - "clothing", - "object", - "notecard", - "category", - "root", - "lsltext", - "lslbyte", - "txtr_tga",// Intentionally spelled this way. Limited to eight characters. - "bodypart", - "trash", - "snapshot", - "lstndfnd", - "snd_wav", - "img_tga", - "jpeg", - "animatn", - "gesture", - "simstate" -}; - -// This table is meant for decoding to human readable form. Put any -// and as many printable characters you want in each one. -// See also llinventory.cpp INVENTORY_TYPE_HUMAN_NAMES -const char* LLAssetType::mAssetTypeHumanNames[LLAssetType::AT_COUNT] = -{ - "texture", - "sound", - "calling card", - "landmark", - "legacy script", - "clothing", - "object", - "note card", - "folder", - "root", - "lsl2 script", - "lsl bytecode", - "tga texture", - "body part", - "trash", - "snapshot", - "lost and found", - "sound", - "targa image", - "jpeg image", - "animation", - "gesture", - "simstate" -}; - -///---------------------------------------------------------------------------- -/// class LLAssetType -///---------------------------------------------------------------------------- - // static -const char* LLAssetType::lookup( LLAssetType::EType type ) +const char *LLAssetType::lookup(LLAssetType::EType asset_type) { - if( (type >= 0) && (type < AT_COUNT )) + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) { - return mAssetTypeNames[ S32( type ) ]; + return entry->mTypeName; } else { @@ -180,30 +160,35 @@ const char* LLAssetType::lookup( LLAssetType::EType type ) } // static -LLAssetType::EType LLAssetType::lookup( const char* name ) +LLAssetType::EType LLAssetType::lookup(const char* name) { return lookup(ll_safe_string(name)); } -LLAssetType::EType LLAssetType::lookup( const std::string& name ) +LLAssetType::EType LLAssetType::lookup(const std::string& type_name) { - for( S32 i = 0; i < AT_COUNT; i++ ) + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (LLAssetDictionary::const_iterator iter = dict->begin(); + iter != dict->end(); + iter++) { - if( name == mAssetTypeNames[i] ) + const AssetEntry *entry = iter->second; + if (type_name == entry->mTypeName) { - // match - return (EType)i; + return iter->first; } } return AT_NONE; } // static -const char* LLAssetType::lookupHumanReadable(LLAssetType::EType type) +const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) { - if( (type >= 0) && (type < AT_COUNT )) + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) { - return mAssetTypeHumanNames[S32(type)]; + return entry->mHumanName; } else { @@ -212,49 +197,94 @@ const char* LLAssetType::lookupHumanReadable(LLAssetType::EType type) } // static -LLAssetType::EType LLAssetType::lookupHumanReadable( const char* name ) +LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) { return lookupHumanReadable(ll_safe_string(name)); } -LLAssetType::EType LLAssetType::lookupHumanReadable( const std::string& name ) +LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) { - for( S32 i = 0; i < AT_COUNT; i++ ) + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (LLAssetDictionary::const_iterator iter = dict->begin(); + iter != dict->end(); + iter++) { - if( name == mAssetTypeHumanNames[i] ) + const AssetEntry *entry = iter->second; + if (readable_name == entry->mHumanName) { - // match - return (EType)i; + return iter->first; } } return AT_NONE; } -EDragAndDropType LLAssetType::lookupDragAndDropType( EType asset ) +// static +const char *LLAssetType::lookupCategoryName(LLAssetType::EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCategoryName; + } + else + { + return "New Folder"; + } +} + +// static +EDragAndDropType LLAssetType::lookupDragAndDropType(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + return entry->mDadType; + else + return DAD_NONE; +} + +// static +bool LLAssetType::lookupCanLink(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanLink; + } + return false; +} + +// static +// Not adding this to dictionary since we probably will only have these two types +bool LLAssetType::lookupIsLinkType(EType asset_type) +{ + if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER) + { + return true; + } + return false; +} + +// static +// Only ensembles and plain folders aren't protected. "Protected" means +// you can't change certain properties such as their type. +bool LLAssetType::lookupIsProtectedCategoryType(EType asset_type) { - switch( asset ) + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) { - case AT_TEXTURE: return DAD_TEXTURE; - case AT_SOUND: return DAD_SOUND; - case AT_CALLINGCARD: return DAD_CALLINGCARD; - case AT_LANDMARK: return DAD_LANDMARK; - case AT_SCRIPT: return DAD_NONE; - case AT_CLOTHING: return DAD_CLOTHING; - case AT_OBJECT: return DAD_OBJECT; - case AT_NOTECARD: return DAD_NOTECARD; - case AT_CATEGORY: return DAD_CATEGORY; - case AT_ROOT_CATEGORY: return DAD_ROOT_CATEGORY; - case AT_LSL_TEXT: return DAD_SCRIPT; - case AT_BODYPART: return DAD_BODYPART; - case AT_ANIMATION: return DAD_ANIMATION; - case AT_GESTURE: return DAD_GESTURE; - default: return DAD_NONE; - }; + return entry->mIsProtected; + } + return true; } + // static. Generate a good default description -void LLAssetType::generateDescriptionFor(LLAssetType::EType type, - std::string& desc) +void LLAssetType::generateDescriptionFor(LLAssetType::EType asset_type, + std::string& description) { const S32 BUF_SIZE = 30; char time_str[BUF_SIZE]; /* Flawfinder: ignore */ @@ -262,6 +292,6 @@ void LLAssetType::generateDescriptionFor(LLAssetType::EType type, time(&now); memset(time_str, '\0', BUF_SIZE); strftime(time_str, BUF_SIZE - 1, "%Y-%m-%d %H:%M:%S ", localtime(&now)); - desc.assign(time_str); - desc.append(LLAssetType::lookupHumanReadable(type)); + description.assign(time_str); + description.append(LLAssetType::lookupHumanReadable(asset_type)); } diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 4077b8d2c1..5c30c8354f 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -30,8 +30,8 @@ * $/LicenseInfo$ */ -#ifndef LL_LLASSETTYPE -#define LL_LLASSETTYPE +#ifndef LL_LLASSETTYPE_H +#define LL_LLASSETTYPE_H #include @@ -42,137 +42,163 @@ class LLAssetType public: enum EType { - // Used for painting the faces of geometry. - // Stored in typical j2c stream format AT_TEXTURE = 0, + // Used for painting the faces of geometry. + // Stored in typical j2c stream format. - // Used to fill the aural spectrum. AT_SOUND = 1, + // Used to fill the aural spectrum. - // Links instant message access to the user on the card. eg, a - // card for yourself, a card for linden support, a card for - // the guy you were talking to in the coliseum. AT_CALLINGCARD = 2, + // Links instant message access to the user on the card. + // : E.G. A card for yourself, for linden support, for + // : the guy you were talking to in the coliseum. - // Links to places in the world with location and a screen - // shot or image saved. eg, home, linden headquarters, the - // coliseum, or destinations where we want to increase - // traffic. AT_LANDMARK = 3, + // Links to places in the world with location and a screen shot or image saved. + // : E.G. Home, linden headquarters, the coliseum, destinations where + // : we want to increase traffic. - // Valid scripts that can be attached to an object. eg. open a - // door, jump into the air. AT_SCRIPT = 4, + // Valid scripts that can be attached to an object. + // : E.G. Open a door, jump into the air. - // A collection of textures and parameters that can be worn - // by an avatar. AT_CLOTHING = 5, + // A collection of textures and parameters that can be worn by an avatar. - // Any combination of textures, sounds, and scripts that are - // associated with a fixed piece of geometry. eg, a hot tub, a - // house with working door. AT_OBJECT = 6, + // Any combination of textures, sounds, and scripts that are + // associated with a fixed piece of geometry. + // : E.G. A hot tub, a house with working door. - // Just text AT_NOTECARD = 7, + // Just text. - // A category holds a collection of inventory items. It's - // treated as an item in the inventory, and therefore needs a - // type. AT_CATEGORY = 8, + // Holds a collection of inventory items. + // It's treated as an item in the inventory and therefore needs a type. - // A root category is a user's root inventory category. We - // decided to expose it visually, so it seems logical to fold - // it into the asset types. AT_ROOT_CATEGORY = 9, + // A user's root inventory category. + // We decided to expose it visually, so it seems logical to fold + // it into the asset types. - // The LSL is the brand spanking new scripting language. We've - // split it into a text and bytecode representation. AT_LSL_TEXT = 10, AT_LSL_BYTECODE = 11, + // The LSL is the scripting language. + // We've split it into a text and bytecode representation. - // uncompressed TGA texture AT_TEXTURE_TGA = 12, + // Uncompressed TGA texture. - // A collection of textures and parameters that can be worn - // by an avatar. AT_BODYPART = 13, + // A collection of textures and parameters that can be worn by an avatar. - // This asset type is meant to only be used as a marker for a - // category preferred type. Using this, we can throw things in - // the trash before completely deleting. AT_TRASH = 14, + // Only to be used as a marker for a category preferred type. + // Using this, we can throw things in the trash before completely deleting. - // This is a marker for a folder meant for snapshots. No - // actual assets will be snapshots, though if there were, you - // could interpret them as textures. AT_SNAPSHOT_CATEGORY = 15, + // A marker for a folder meant for snapshots. + // No actual assets will be snapshots, though if there were, you + // could interpret them as textures. - // This is used to stuff lost&found items into AT_LOST_AND_FOUND = 16, + // Used to stuff lost&found items into. - // uncompressed sound AT_SOUND_WAV = 17, + // Uncompressed sound. - // uncompressed image, non-square, and not appropriate for use - // as a texture. AT_IMAGE_TGA = 18, + // Uncompressed image, non-square. + // Not appropriate for use as a texture. - // compressed image, non-square, and not appropriate for use - // as a texture. AT_IMAGE_JPEG = 19, + // Compressed image, non-square. + // Not appropriate for use as a texture. - // animation AT_ANIMATION = 20, + // Animation. - // gesture, sequence of animations, sounds, chat, wait steps AT_GESTURE = 21, + // Gesture, sequence of animations, sounds, chat, wait steps. - // simstate file AT_SIMSTATE = 22, + // Simstate file. - // +*********************************************+ - // | TO ADD AN ELEMENT TO THIS ENUM: | - // +*********************************************+ - // | 1. INSERT BEFORE AT_COUNT | - // | 2. INCREMENT AT_COUNT BY 1 | - // | 3. ADD TO LLAssetType::mAssetTypeNames | - // | 4. ADD TO LLAssetType::mAssetTypeHumanNames | - // +*********************************************+ + AT_FAVORITE = 23, + // favorite items - AT_COUNT = 23, + AT_LINK = 24, + // Inventory symbolic link + + AT_LINK_FOLDER = 25, + // Inventory folder link + + AT_FOLDER_ENSEMBLE_START = 26, + AT_FOLDER_ENSEMBLE_END = 45, + // This range is reserved for special clothing folder types. + + AT_CURRENT_OUTFIT = 46, + // Current outfit + + AT_OUTFIT = 47, + // Predefined outfit ("look") + + AT_MY_OUTFITS = 48, + // Folder that holds your outfits. + + + AT_COUNT = 49, + + // +*********************************************************+ + // | TO ADD AN ELEMENT TO THIS ENUM: | + // +*********************************************************+ + // | 1. INSERT BEFORE AT_COUNT | + // | 2. INCREMENT AT_COUNT BY 1 | + // | 3. ADD TO LLAssetDictionary in LLAssetType.cpp | + // | 3. ADD TO DEFAULT_ASSET_FOR_INV in LLInventoryType.cpp | + // +*********************************************************+ AT_NONE = -1 }; // machine transation between type and strings - static EType lookup(const char* name); // safe conversion to std::string, *TODO: deprecate - static EType lookup(const std::string& name); - static const char* lookup(EType type); + static EType lookup(const char* name); // safe conversion to std::string, *TODO: deprecate + static EType lookup(const std::string& type_name); + static const char* lookup(EType asset_type); // translation from a type to a human readable form. - static EType lookupHumanReadable( const char* name ); // safe conversion to std::string, *TODO: deprecate - static EType lookupHumanReadable( const std::string& name ); - static const char* lookupHumanReadable(EType type); - - static EDragAndDropType lookupDragAndDropType( EType ); + static EType lookupHumanReadable(const char* desc_name); // safe conversion to std::string, *TODO: deprecate + static EType lookupHumanReadable(const std::string& readable_name); + static const char* lookupHumanReadable(EType asset_type); // Generate a good default description. You may want to add a verb // or agent name after this depending on your application. - static void generateDescriptionFor(LLAssetType::EType type, - std::string& desc); + static void generateDescriptionFor(LLAssetType::EType asset_type, + std::string& description); + + static EType getType(const std::string& desc_name); + static const std::string& getDesc(EType asset_type); + static EDragAndDropType lookupDragAndDropType(EType asset_type); + + static bool lookupCanLink(EType asset_type); + static bool lookupIsLinkType(EType asset_type); - static EType getType(const std::string& sin); - static std::string getDesc(EType type); + static const char* lookupCategoryName(EType asset_type); + static bool lookupIsProtectedCategoryType(EType asset_type); + + /* TODO: Change return types from "const char *" to "const std::string &". + This is fairly straightforward, but requires changing some calls to use .c_str(). + e.g.: + - fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); + + fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType).c_str()); + */ private: // don't instantiate or derive one of these objects LLAssetType( void ) {} ~LLAssetType( void ) {} - -private: - static const char* mAssetTypeNames[]; - static const char* mAssetTypeHumanNames[]; }; -#endif // LL_LLASSETTYPE +#endif // LL_LLASSETTYPE_H diff --git a/indra/llcommon/lldictionary.h b/indra/llcommon/lldictionary.h new file mode 100644 index 0000000000..436b689ca6 --- /dev/null +++ b/indra/llcommon/lldictionary.h @@ -0,0 +1,106 @@ +/** + * @file lldictionary.h + * @brief Lldictionary class header file + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2007, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLDICTIONARY_H +#define LL_LLDICTIONARY_H + +#include + +struct LLDictionaryEntry +{ + LLDictionaryEntry(const std::string &name) : + mName(name) + { + mNameCapitalized = mName; + LLStringUtil::replaceChar(mNameCapitalized, '-', ' '); + LLStringUtil::replaceChar(mNameCapitalized, '_', ' '); + for (U32 i=0; i < mNameCapitalized.size(); i++) + { + if (i == 0 || mNameCapitalized[i-1] == ' ') // don't change ordering of this statement or crash + { + mNameCapitalized[i] = toupper(mNameCapitalized[i]); + } + } + } + virtual ~LLDictionaryEntry() {} + const std::string mName; + std::string mNameCapitalized; +}; + +template +class LLDictionary : public std::map +{ +public: + typedef std::map map_t; + typedef typename map_t::iterator iterator_t; + typedef typename map_t::const_iterator const_iterator_t; + + LLDictionary() {} + virtual ~LLDictionary() + { + for (iterator_t iter = map_t::begin(); iter != map_t::end(); ++iter) + delete (iter->second); + } + + const Entry *lookup(Index index) const + { + const_iterator_t dictionary_iter = map_t::find(index); + if (dictionary_iter == map_t::end()) return NULL; + return dictionary_iter->second; + } + const Index lookup(const std::string &name) const + { + for (const_iterator_t dictionary_iter = map_t::begin(); + dictionary_iter != map_t::end(); + dictionary_iter++) + { + const Entry *entry = dictionary_iter->second; + if (entry->mName == name) + { + return dictionary_iter->first; + } + } + llassert(false); + return Index(-1); + } + +protected: + void addEntry(Index index, Entry *entry) + { + if (lookup(index)) + { + llerrs << "Dictionary entry already added (attempted to add duplicate entry)" << llendl; + } + (*this)[index] = entry; + } +}; + +#endif // LL_LLDICTIONARY_H diff --git a/indra/llcommon/stdenums.h b/indra/llcommon/stdenums.h index 41da51fce3..1a5678dde1 100644 --- a/indra/llcommon/stdenums.h +++ b/indra/llcommon/stdenums.h @@ -54,7 +54,8 @@ enum EDragAndDropType DAD_BODYPART = 11, DAD_ANIMATION = 12, DAD_GESTURE = 13, - DAD_COUNT = 14, // number of types in this enum + DAD_LINK = 14, + DAD_COUNT = 15, // number of types in this enum }; // Reasons for drags to be denied. diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index 2823cf7be9..e45bb59881 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -126,6 +126,20 @@ const std::string& LLInventoryObject::getName() const return mName; } +// To bypass linked items, since llviewerinventory's getType +// will return the linked-to item's type instead of this object's type. +LLAssetType::EType LLInventoryObject::getActualType() const +{ + return mType; +} + +// See LLInventoryItem override. +// virtual +const LLUUID& LLInventoryObject::getLinkedUUID() const +{ + return mUUID; +} + LLAssetType::EType LLInventoryObject::getType() const { return mType; @@ -296,6 +310,7 @@ LLInventoryItem::LLInventoryItem( { LLStringUtil::replaceNonstandardASCII(mDescription, ' '); LLStringUtil::replaceChar(mDescription, '|', ' '); + mPermissions.initMasks(inv_type); } LLInventoryItem::LLInventoryItem() : @@ -333,6 +348,19 @@ void LLInventoryItem::copyItem(const LLInventoryItem* other) mCreationDate = other->mCreationDate; } +// If this is a linked item, then the UUID of the base object is +// this item's assetID. +// virtual +const LLUUID& LLInventoryItem::getLinkedUUID() const +{ + if (LLAssetType::lookupIsLinkType(getActualType())) + { + return mAssetUUID; + } + + return LLInventoryObject::getLinkedUUID(); +} + const LLPermissions& LLInventoryItem::getPermissions() const { return mPermissions; @@ -405,6 +433,9 @@ void LLInventoryItem::setDescription(const std::string& d) void LLInventoryItem::setPermissions(const LLPermissions& perm) { mPermissions = perm; + + // Override permissions to unrestricted if this is a landmark + mPermissions.initMasks(mInventoryType); } void LLInventoryItem::setInventoryType(LLInventoryType::EType inv_type) @@ -476,6 +507,7 @@ BOOL LLInventoryItem::unpackMessage(LLMessageSystem* msg, const char* block, S32 mType = static_cast(type); msg->getS8(block, "InvType", type, block_num); mInventoryType = static_cast(type); + mPermissions.initMasks(mInventoryType); msg->getU32Fast(block, _PREHASH_Flags, mFlags, block_num); @@ -666,6 +698,9 @@ BOOL LLInventoryItem::importFile(LLFILE* fp) lldebugs << "Resetting inventory type for " << mUUID << llendl; mInventoryType = LLInventoryType::defaultForAssetType(mType); } + + mPermissions.initMasks(mInventoryType); + return success; } @@ -705,8 +740,8 @@ BOOL LLInventoryItem::exportFile(LLFILE* fp, BOOL include_asset_key) const fprintf(fp, "\t\tasset_id\t%s\n", uuid_str.c_str()); } fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); - const char* inv_type_str = LLInventoryType::lookup(mInventoryType); - if(inv_type_str) fprintf(fp, "\t\tinv_type\t%s\n", inv_type_str); + const std::string inv_type_str = LLInventoryType::lookup(mInventoryType); + if(!inv_type_str.empty()) fprintf(fp, "\t\tinv_type\t%s\n", inv_type_str.c_str()); fprintf(fp, "\t\tflags\t%08x\n", mFlags); mSaleInfo.exportFile(fp); fprintf(fp, "\t\tname\t%s|\n", mName.c_str()); @@ -869,6 +904,9 @@ BOOL LLInventoryItem::importLegacyStream(std::istream& input_stream) lldebugs << "Resetting inventory type for " << mUUID << llendl; mInventoryType = LLInventoryType::defaultForAssetType(mType); } + + mPermissions.initMasks(mInventoryType); + return success; } @@ -908,8 +946,8 @@ BOOL LLInventoryItem::exportLegacyStream(std::ostream& output_stream, BOOL inclu output_stream << "\t\tasset_id\t" << uuid_str << "\n"; } output_stream << "\t\ttype\t" << LLAssetType::lookup(mType) << "\n"; - const char* inv_type_str = LLInventoryType::lookup(mInventoryType); - if(inv_type_str) + const std::string inv_type_str = LLInventoryType::lookup(mInventoryType); + if(!inv_type_str.empty()) output_stream << "\t\tinv_type\t" << inv_type_str << "\n"; std::string buffer; buffer = llformat( "\t\tflags\t%08x\n", mFlags); @@ -951,8 +989,8 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const } sd[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType); sd[INV_INVENTORY_TYPE_LABEL] = mInventoryType; - const char* inv_type_str = LLInventoryType::lookup(mInventoryType); - if(inv_type_str) + const std::string inv_type_str = LLInventoryType::lookup(mInventoryType); + if(!inv_type_str.empty()) { sd[INV_INVENTORY_TYPE_LABEL] = inv_type_str; } @@ -1091,6 +1129,8 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd) mInventoryType = LLInventoryType::defaultForAssetType(mType); } + mPermissions.initMasks(mInventoryType); + return true; fail: return false; @@ -1698,7 +1738,7 @@ LLSD ll_create_sd_from_inventory_category(LLPointer cat) rv[INV_PARENT_ID_LABEL] = cat->getParentUUID(); rv[INV_NAME_LABEL] = cat->getName(); rv[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(cat->getType()); - if(LLAssetType::AT_NONE != cat->getPreferredType()) + if(LLAssetType::lookupIsProtectedCategoryType(cat->getPreferredType())) { rv[INV_PREFERRED_TYPE_LABEL] = LLAssetType::lookup(cat->getPreferredType()); diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h index d34046c310..094aebe93b 100644 --- a/indra/llinventory/llinventory.h +++ b/indra/llinventory/llinventory.h @@ -91,8 +91,11 @@ public: // accessors virtual const LLUUID& getUUID() const; const LLUUID& getParentUUID() const; - const std::string& getName() const; - LLAssetType::EType getType() const; + virtual const LLUUID& getLinkedUUID() const; // get the inventoryID that this item points to, else this item's inventoryID + + virtual const std::string& getName() const; + virtual LLAssetType::EType getType() const; + LLAssetType::EType getActualType() const; // bypasses indirection for linked items // mutators - will not call updateServer(); void setUUID(const LLUUID& new_uuid); @@ -238,15 +241,16 @@ public: void generateUUID() { mUUID.generate(); } // accessors - const LLPermissions& getPermissions() const; - const LLUUID& getCreatorUUID() const; - const LLUUID& getAssetUUID() const; - const std::string& getDescription() const; - const LLSaleInfo& getSaleInfo() const; - LLInventoryType::EType getInventoryType() const; - U32 getFlags() const; - time_t getCreationDate() const; - U32 getCRC32() const; // really more of a checksum. + virtual const LLUUID& getLinkedUUID() const; + virtual const LLPermissions& getPermissions() const; + virtual const LLUUID& getCreatorUUID() const; + virtual const LLUUID& getAssetUUID() const; + virtual const std::string& getDescription() const; + virtual const LLSaleInfo& getSaleInfo() const; + virtual LLInventoryType::EType getInventoryType() const; + virtual U32 getFlags() const; + virtual time_t getCreationDate() const; + virtual U32 getCRC32() const; // really more of a checksum. // mutators - will not call updateServer(), and will never fail // (though it may correct to sane values) diff --git a/indra/llinventory/llinventorytype.cpp b/indra/llinventory/llinventorytype.cpp index a161a0ee00..866d6722a0 100644 --- a/indra/llinventory/llinventorytype.cpp +++ b/indra/llinventory/llinventorytype.cpp @@ -33,66 +33,72 @@ #include "linden_common.h" #include "llinventorytype.h" +#include "lldictionary.h" +#include "llmemory.h" + +static const std::string empty_string; ///---------------------------------------------------------------------------- /// Class LLInventoryType ///---------------------------------------------------------------------------- - -// Unlike asset type names, not limited to 8 characters. -// Need not match asset type names. -static const char* INVENTORY_TYPE_NAMES[LLInventoryType::IT_COUNT] = -{ - "texture", // 0 - "sound", - "callcard", - "landmark", - NULL, - NULL, // 5 - "object", - "notecard", - "category", - "root", - "script", // 10 - NULL, - NULL, - NULL, - NULL, - "snapshot", // 15 - NULL, - "attach", - "wearable", - "animation", - "gesture", // 20 +struct InventoryEntry : public LLDictionaryEntry +{ + InventoryEntry(const std::string &name, // unlike asset type names, not limited to 8 characters; need not match asset type names + const std::string &human_name, // for decoding to human readable form; put any and as many printable characters you want in each one. + int num_asset_types = 0, ...) + : + LLDictionaryEntry(name), + mHumanName(human_name) + { + va_list argp; + va_start(argp, num_asset_types); + // Read in local textures + for (U8 i=0; i < num_asset_types; i++) + { + LLAssetType::EType t = (LLAssetType::EType)va_arg(argp,int); + mAssetTypes.push_back(t); + } + } + + const std::string mHumanName; + typedef std::vector asset_vec_t; + asset_vec_t mAssetTypes; }; -// This table is meant for decoding to human readable form. Put any -// and as many printable characters you want in each one. -// See also LLAssetType::mAssetTypeHumanNames -static const char* INVENTORY_TYPE_HUMAN_NAMES[LLInventoryType::IT_COUNT] = -{ - "texture", // 0 - "sound", - "calling card", - "landmark", - NULL, - NULL, // 5 - "object", - "note card", - "folder", - "root", - "script", // 10 - NULL, - NULL, - NULL, - NULL, - "snapshot", // 15 - NULL, - "attachment", - "wearable", - "animation", - "gesture", // 20 +class LLInventoryDictionary : public LLSingleton, + public LLDictionary +{ +public: + LLInventoryDictionary(); }; +LLInventoryDictionary::LLInventoryDictionary() +{ + addEntry(LLInventoryType::IT_TEXTURE, new InventoryEntry("texture", "texture", 1, LLAssetType::AT_TEXTURE)); + addEntry(LLInventoryType::IT_SOUND, new InventoryEntry("sound", "sound", 1, LLAssetType::AT_SOUND)); + addEntry(LLInventoryType::IT_CALLINGCARD, new InventoryEntry("callcard", "calling card", 1, LLAssetType::AT_CALLINGCARD)); + addEntry(LLInventoryType::IT_LANDMARK, new InventoryEntry("landmark", "landmark", 1, LLAssetType::AT_LANDMARK)); + //addEntry(LLInventoryType::IT_SCRIPT, new InventoryEntry(NULL,NULL)); + //addEntry(LLInventoryType::IT_CLOTHING, new InventoryEntry(NULL,NULL)); + addEntry(LLInventoryType::IT_OBJECT, new InventoryEntry("object", "object", 1, LLAssetType::AT_OBJECT)); + addEntry(LLInventoryType::IT_NOTECARD, new InventoryEntry("notecard", "note card", 1, LLAssetType::AT_NOTECARD)); + addEntry(LLInventoryType::IT_CATEGORY, new InventoryEntry("category", "folder" )); + addEntry(LLInventoryType::IT_ROOT_CATEGORY, new InventoryEntry("root", "root" )); + addEntry(LLInventoryType::IT_LSL, new InventoryEntry("script", "script", 2, LLAssetType::AT_LSL_TEXT, LLAssetType::AT_LSL_BYTECODE)); + //addEntry(LLInventoryType::IT_LSL_BYTECODE, new InventoryEntry(NULL,NULL)); + //addEntry(LLInventoryType::IT_TEXTURE_TGA, new InventoryEntry(NULL,NULL)); + //addEntry(LLInventoryType::IT_BODYPART, new InventoryEntry(NULL,NULL)); + //addEntry(LLInventoryType::IT_TRASH, new InventoryEntry(NULL,NULL)); + addEntry(LLInventoryType::IT_SNAPSHOT, new InventoryEntry("snapshot", "snapshot", 1, LLAssetType::AT_TEXTURE)); + //addEntry(LLInventoryType::IT_LOST_AND_FOUND, new InventoryEntry(NULL,NULL, )); + addEntry(LLInventoryType::IT_ATTACHMENT, new InventoryEntry("attach", "attachment", 1, LLAssetType::AT_OBJECT)); + addEntry(LLInventoryType::IT_WEARABLE, new InventoryEntry("wearable", "wearable", 2, LLAssetType::AT_CLOTHING, LLAssetType::AT_BODYPART)); + addEntry(LLInventoryType::IT_ANIMATION, new InventoryEntry("animation", "animation", 1, LLAssetType::AT_ANIMATION)); + addEntry(LLInventoryType::IT_GESTURE, new InventoryEntry("gesture", "gesture", 1, LLAssetType::AT_GESTURE)); + addEntry(LLInventoryType::IT_FAVORITE, new InventoryEntry("favorite", "favorite", 1, LLAssetType::AT_FAVORITE)); +} + + // Maps asset types to the default inventory type for that kind of asset. // Thus, "Lost and Found" is a "Category" static const LLInventoryType::EType @@ -120,76 +126,60 @@ DEFAULT_ASSET_FOR_INV_TYPE[LLAssetType::AT_COUNT] = LLInventoryType::IT_NONE, // AT_IMAGE_JPEG LLInventoryType::IT_ANIMATION, // AT_ANIMATION LLInventoryType::IT_GESTURE, // AT_GESTURE -}; + LLInventoryType::IT_NONE, // AT_SIMSTATE + LLInventoryType::IT_FAVORITE, // AT_FAVORITE -static const int MAX_POSSIBLE_ASSET_TYPES = 2; -static const LLAssetType::EType -INVENTORY_TO_ASSET_TYPE[LLInventoryType::IT_COUNT][MAX_POSSIBLE_ASSET_TYPES] = -{ - { LLAssetType::AT_TEXTURE, LLAssetType::AT_NONE }, // IT_TEXTURE - { LLAssetType::AT_SOUND, LLAssetType::AT_NONE }, // IT_SOUND - { LLAssetType::AT_CALLINGCARD, LLAssetType::AT_NONE }, // IT_CALLINGCARD - { LLAssetType::AT_LANDMARK, LLAssetType::AT_NONE }, // IT_LANDMARK - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_OBJECT, LLAssetType::AT_NONE }, // IT_OBJECT - { LLAssetType::AT_NOTECARD, LLAssetType::AT_NONE }, // IT_NOTECARD - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, // IT_CATEGORY - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, // IT_ROOT_CATEGORY - { LLAssetType::AT_LSL_TEXT, LLAssetType::AT_LSL_BYTECODE }, // IT_LSL - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_TEXTURE, LLAssetType::AT_NONE }, // IT_SNAPSHOT - { LLAssetType::AT_NONE, LLAssetType::AT_NONE }, - { LLAssetType::AT_OBJECT, LLAssetType::AT_NONE }, // IT_ATTACHMENT - { LLAssetType::AT_CLOTHING, LLAssetType::AT_BODYPART }, // IT_WEARABLE - { LLAssetType::AT_ANIMATION, LLAssetType::AT_NONE }, // IT_ANIMATION - { LLAssetType::AT_GESTURE, LLAssetType::AT_NONE }, // IT_GESTURE + LLInventoryType::IT_NONE, // AT_LINK + LLInventoryType::IT_NONE, // AT_LINK_FOLDER + + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + LLInventoryType::IT_CATEGORY, // AT_ENSEMBLE + + LLInventoryType::IT_CATEGORY, // AT_CURRENT_OUTFIT + LLInventoryType::IT_CATEGORY, // AT_OUTFIT + LLInventoryType::IT_CATEGORY, // AT_MY_OUTFITS }; // static -const char* LLInventoryType::lookup(EType type) +const std::string &LLInventoryType::lookup(EType type) { - if((type >= 0) && (type < IT_COUNT)) - { - return INVENTORY_TYPE_NAMES[S32(type)]; - } - else - { - return NULL; - } + const InventoryEntry *entry = LLInventoryDictionary::getInstance()->lookup(type); + if (!entry) return empty_string; + return entry->mName; } // static LLInventoryType::EType LLInventoryType::lookup(const std::string& name) { - for(S32 i = 0; i < IT_COUNT; ++i) - { - if((INVENTORY_TYPE_NAMES[i]) - && (name == INVENTORY_TYPE_NAMES[i])) - { - // match - return (EType)i; - } - } - return IT_NONE; + return LLInventoryDictionary::getInstance()->lookup(name); } // XUI:translate // translation from a type to a human readable form. // static -const char* LLInventoryType::lookupHumanReadable(EType type) +const std::string &LLInventoryType::lookupHumanReadable(EType type) { - if((type >= 0) && (type < IT_COUNT)) - { - return INVENTORY_TYPE_HUMAN_NAMES[S32(type)]; - } - else - { - return NULL; - } + const InventoryEntry *entry = LLInventoryDictionary::getInstance()->lookup(type); + if (!entry) return empty_string; + return entry->mHumanName; } // return the default inventory for the given asset type. @@ -206,21 +196,36 @@ LLInventoryType::EType LLInventoryType::defaultForAssetType(LLAssetType::EType a } } -bool inventory_and_asset_types_match( - LLInventoryType::EType inventory_type, - LLAssetType::EType asset_type) + +// add any types that we don't want the user to be able to change permissions on. +// static +bool LLInventoryType::cannotRestrictPermissions(LLInventoryType::EType type) { - bool rv = false; - if((inventory_type >= 0) && (inventory_type < LLInventoryType::IT_COUNT)) + switch(type) + { + case IT_CALLINGCARD: + case IT_LANDMARK: + return true; + default: + return false; + } +} + +bool inventory_and_asset_types_match(LLInventoryType::EType inventory_type, + LLAssetType::EType asset_type) +{ + const InventoryEntry *entry = LLInventoryDictionary::getInstance()->lookup(inventory_type); + if (!entry) return false; + + for (InventoryEntry::asset_vec_t::const_iterator iter = entry->mAssetTypes.begin(); + iter != entry->mAssetTypes.end(); + iter++) { - for(S32 i = 0; i < MAX_POSSIBLE_ASSET_TYPES; ++i) + const LLAssetType::EType type = (*iter); + if(type == asset_type) { - if(INVENTORY_TO_ASSET_TYPE[inventory_type][i] == asset_type) - { - rv = true; - break; - } + return true; } } - return rv; + return false; } diff --git a/indra/llinventory/llinventorytype.h b/indra/llinventory/llinventorytype.h index d3effc0e6d..8961ff96e7 100644 --- a/indra/llinventory/llinventorytype.h +++ b/indra/llinventory/llinventorytype.h @@ -67,21 +67,25 @@ public: IT_WEARABLE = 18, IT_ANIMATION = 19, IT_GESTURE = 20, - IT_COUNT = 21, + IT_FAVORITE = 21, + IT_COUNT = 22, IT_NONE = -1 }; // machine transation between type and strings static EType lookup(const std::string& name); - static const char* lookup(EType type); + static const std::string &lookup(EType type); // translation from a type to a human readable form. - static const char* lookupHumanReadable(EType type); + static const std::string &lookupHumanReadable(EType type); // return the default inventory for the given asset type. static EType defaultForAssetType(LLAssetType::EType asset_type); + // true if this type cannot have restricted permissions. + static bool cannotRestrictPermissions(EType type); + private: // don't instantiate or derive one of these objects LLInventoryType( void ); diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h index 5ba32c0600..5a865d27ba 100644 --- a/indra/llinventory/llparcel.h +++ b/indra/llinventory/llparcel.h @@ -446,8 +446,10 @@ public: BOOL getAllowFly() const { return (mParcelFlags & PF_ALLOW_FLY) ? TRUE : FALSE; } + // Remove permission restrictions for creating landmarks. + // We should eventually remove this flag completely. BOOL getAllowLandmark() const - { return (mParcelFlags & PF_ALLOW_LANDMARK) ? TRUE : FALSE; } + { return TRUE; } BOOL getAllowGroupScripts() const { return (mParcelFlags & PF_ALLOW_GROUP_SCRIPTS) ? TRUE : FALSE; } diff --git a/indra/llinventory/llpermissions.cpp b/indra/llinventory/llpermissions.cpp index e4f8b0dffd..036231ccf8 100644 --- a/indra/llinventory/llpermissions.cpp +++ b/indra/llinventory/llpermissions.cpp @@ -83,6 +83,17 @@ void LLPermissions::initMasks(PermissionMask base, PermissionMask owner, fix(); } +// ! BACKWARDS COMPATIBILITY ! Override masks for inventory types that +// no longer can have restricted permissions. This takes care of previous +// version landmarks that could have had no copy/mod/transfer bits set. +void LLPermissions::initMasks(LLInventoryType::EType type) +{ + if (LLInventoryType::cannotRestrictPermissions(type)) + { + initMasks(PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL, PERM_ALL); + } +} + BOOL LLPermissions::getOwnership(LLUUID& owner_id, BOOL& is_group_owned) const { if(mOwner.notNull()) diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h index 9280629e0c..f03045e265 100644 --- a/indra/llinventory/llpermissions.h +++ b/indra/llinventory/llpermissions.h @@ -38,6 +38,7 @@ #include "lluuid.h" #include "llxmlnode.h" #include "reflective.h" +#include "llinventorytype.h" // prototypes class LLMessageSystem; @@ -129,6 +130,8 @@ public: void initMasks(PermissionMask base, PermissionMask owner, PermissionMask everyone, PermissionMask group, PermissionMask next); + // adjust permissions based on inventory type. + void initMasks(LLInventoryType::EType type); // // ACCESSORS diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index a4d59275b9..5a1cd95ffc 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -264,16 +264,17 @@ bool is_asset_fetch_by_id_allowed(LLAssetType::EType type) bool rv = false; switch(type) { - case LLAssetType::AT_SOUND: - case LLAssetType::AT_LANDMARK: - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_ANIMATION: - case LLAssetType::AT_GESTURE: - rv = true; - break; - default: - break; + case LLAssetType::AT_SOUND: + case LLAssetType::AT_LANDMARK: + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_ANIMATION: + case LLAssetType::AT_GESTURE: + case LLAssetType::AT_FAVORITE: + rv = true; + break; + default: + break; } return rv; } @@ -284,18 +285,21 @@ bool is_asset_id_knowable(LLAssetType::EType type) bool rv = false; switch(type) { - case LLAssetType::AT_TEXTURE: - case LLAssetType::AT_SOUND: - case LLAssetType::AT_LANDMARK: - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_NOTECARD: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_ANIMATION: - case LLAssetType::AT_GESTURE: - rv = true; - break; - default: - break; + case LLAssetType::AT_TEXTURE: + case LLAssetType::AT_SOUND: + case LLAssetType::AT_LANDMARK: + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_NOTECARD: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_ANIMATION: + case LLAssetType::AT_GESTURE: + case LLAssetType::AT_FAVORITE: + case LLAssetType::AT_LINK: + case LLAssetType::AT_LINK_FOLDER: + rv = true; + break; + default: + break; } return rv; } diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index 43b5f8e224..30af58e430 100644 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -174,6 +174,7 @@ char* _PREHASH_UpdateInventoryItem = LLMessageStringTable::getInstance()->getStr char* _PREHASH_UpdateCreateInventoryItem = LLMessageStringTable::getInstance()->getString("UpdateCreateInventoryItem"); char* _PREHASH_MoveInventoryItem = LLMessageStringTable::getInstance()->getString("MoveInventoryItem"); char* _PREHASH_CopyInventoryItem = LLMessageStringTable::getInstance()->getString("CopyInventoryItem"); +char* _PREHASH_LinkInventoryItem = LLMessageStringTable::getInstance()->getString("LinkInventoryItem"); char* _PREHASH_RemoveInventoryItem = LLMessageStringTable::getInstance()->getString("RemoveInventoryItem"); char* _PREHASH_CreateInventoryItem = LLMessageStringTable::getInstance()->getString("CreateInventoryItem"); char* _PREHASH_PathTwistBegin = LLMessageStringTable::getInstance()->getString("PathTwistBegin"); diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h index f8ef610408..e73ec3e5e1 100644 --- a/indra/llmessage/message_prehash.h +++ b/indra/llmessage/message_prehash.h @@ -174,6 +174,7 @@ extern char * _PREHASH_UpdateInventoryItem; extern char * _PREHASH_UpdateCreateInventoryItem; extern char * _PREHASH_MoveInventoryItem; extern char * _PREHASH_CopyInventoryItem; +extern char * _PREHASH_LinkInventoryItem; extern char * _PREHASH_RemoveInventoryItem; extern char * _PREHASH_CreateInventoryItem; extern char * _PREHASH_PathTwistBegin; diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 5d09d8748f..d96bcf5bbf 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -602,13 +602,12 @@ void LLInvFVBridge::changeCategoryParent(LLInventoryModel* model, } -const char* safe_inv_type_lookup(LLInventoryType::EType inv_type) +const std::string safe_inv_type_lookup(LLInventoryType::EType inv_type) { - const char* rv = LLInventoryType::lookup(inv_type); - if(!rv) + const std::string rv= LLInventoryType::lookup(inv_type); + if(rv.empty()) { - const char* INVALID_TYPE = ""; - rv = INVALID_TYPE; + return std::string(""); } return rv; } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index f47d0777b0..bad2e174c9 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -307,8 +307,8 @@ bool LLViewerInventoryItem::exportFileLocal(LLFILE* fp) const fprintf(fp, "\t\tparent_id\t%s\n", uuid_str.c_str()); mPermissions.exportFile(fp); fprintf(fp, "\t\ttype\t%s\n", LLAssetType::lookup(mType)); - const char* inv_type_str = LLInventoryType::lookup(mInventoryType); - if(inv_type_str) fprintf(fp, "\t\tinv_type\t%s\n", inv_type_str); + const std::string inv_type_str = LLInventoryType::lookup(mInventoryType); + if(!inv_type_str.empty()) fprintf(fp, "\t\tinv_type\t%s\n", inv_type_str.c_str()); fprintf(fp, "\t\tname\t%s|\n", mName.c_str()); fprintf(fp, "\t\tcreation_date\t%d\n", (S32) mCreationDate); fprintf(fp,"\t}\n"); diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg index 23efd65c45..67233cbda0 100644 --- a/scripts/messages/message_template.msg +++ b/scripts/messages/message_template.msg @@ -8945,3 +8945,21 @@ version 2.0 { CRC U32 } } } + +{ + LinkInventoryItem Low 426 NotTrusted Zerocoded + { + AgentData Single + { AgentID LLUUID } + { SessionID LLUUID } + } + { + InventoryData Variable + { CallbackID U32 } // Async Response + { FolderID LLUUID } + { OldItemID LLUUID } + { Name Variable 1 } + { AssetType U8 } + } +} + -- cgit v1.3 From 87776b19443030bece31c26290d1092bf6cbb3e6 Mon Sep 17 00:00:00 2001 From: Christian Goetze Date: Wed, 29 Jul 2009 22:16:52 +0000 Subject: svn merge -r128774:128808 svn+ssh://svn.lindenlab.com/svn/user/cg/qar-1737 effective merge: svn merge -r127126:128746 svn+ssh://svn.lindenlab.com/svn/linden/branches/server/server-1.27 --- indra/llinventory/llinventory.cpp | 33 +++++++ indra/llinventory/llinventory.h | 4 + indra/llmessage/llmail.cpp | 18 +++- indra/llprimitive/llprimitive.cpp | 187 +++++++++++++++++++++++++++++++++++++- 4 files changed, 238 insertions(+), 4 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index e45bb59881..76de357e2b 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -453,6 +453,39 @@ void LLInventoryItem::setCreationDate(time_t creation_date_utc) mCreationDate = creation_date_utc; } +void LLInventoryItem::accumulatePermissionSlamBits(const LLInventoryItem& old_item) +{ + // Remove any pre-existing II_FLAGS_PERM_OVERWRITE_MASK flags + // because we now detect when they should be set. + setFlags( old_item.getFlags() | (getFlags() & ~(LLInventoryItem::II_FLAGS_PERM_OVERWRITE_MASK)) ); + + // Enforce the PERM_OVERWRITE flags for any masks that are different + // but only for AT_OBJECT's since that is the only asset type that can + // exist in-world (instead of only in-inventory or in-object-contents). + if (LLAssetType::AT_OBJECT == getType()) + { + LLPermissions old_permissions = old_item.getPermissions(); + U32 flags_to_be_set = 0; + if(old_permissions.getMaskNextOwner() != getPermissions().getMaskNextOwner()) + { + flags_to_be_set |= LLInventoryItem::II_FLAGS_OBJECT_SLAM_PERM; + } + if(old_permissions.getMaskEveryone() != getPermissions().getMaskEveryone()) + { + flags_to_be_set |= LLInventoryItem::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; + } + if(old_permissions.getMaskGroup() != getPermissions().getMaskGroup()) + { + flags_to_be_set |= LLInventoryItem::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; + } + LLSaleInfo old_sale_info = old_item.getSaleInfo(); + if(old_sale_info != getSaleInfo()) + { + flags_to_be_set |= LLInventoryItem::II_FLAGS_OBJECT_SLAM_SALE; + } + setFlags(getFlags() | flags_to_be_set); + } +} const LLSaleInfo& LLInventoryItem::getSaleInfo() const { diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h index 094aebe93b..08e3958533 100644 --- a/indra/llinventory/llinventory.h +++ b/indra/llinventory/llinventory.h @@ -262,6 +262,10 @@ public: void setFlags(U32 flags); void setCreationDate(time_t creation_date_utc); + // Check for changes in permissions masks and sale info + // and set the corresponding bits in mFlags + void accumulatePermissionSlamBits(const LLInventoryItem& old_item); + // Put this inventory item onto the current outgoing mesage. It // assumes you have already called nextBlock(). virtual void packMessage(LLMessageSystem* msg) const; diff --git a/indra/llmessage/llmail.cpp b/indra/llmessage/llmail.cpp index d52ff6c7e8..ce206d8d7d 100644 --- a/indra/llmessage/llmail.cpp +++ b/indra/llmessage/llmail.cpp @@ -265,7 +265,7 @@ std::string LLMail::buildSMTPTransaction( // static bool LLMail::send( const std::string& header, - const std::string& message, + const std::string& raw_message, const char* from_address, const char* to_address) { @@ -276,8 +276,20 @@ bool LLMail::send( return false; } - // *FIX: this translation doesn't deal with a single period on a - // line by itself. + // remove any "." SMTP commands to prevent injection (DEV-35777) + // we don't need to worry about "\r\n.\r\n" because of the + // "\n" --> "\n\n" conversion going into rfc2822_msg below + std::string message = raw_message; + std::string bad_string = "\n.\n"; + std::string good_string = "\n..\n"; + while (1) + { + int index = message.find(bad_string); + if (index == std::string::npos) break; + message.replace(index, bad_string.size(), good_string); + } + + // convert all "\n" into "\r\n" std::ostringstream rfc2822_msg; for(U32 i = 0; i < message.size(); ++i) { diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index 58aaf97a76..7b755a7d17 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -746,16 +746,201 @@ BOOL LLPrimitive::setVolume(const LLVolumeParams &volume_params, const S32 detai U32 old_face_mask = mVolumep->mFaceMask; + S32 face_bit = 0; + S32 cur_mask = 0; + + // Grab copies of the old faces from the original shape, ordered by type. + // We will use these to figure out what old texture info gets mapped to new + // faces in the new shape. + std::vector old_faces; + for (S32 face = 0; face < mVolumep->getNumFaces(); face++) + { + old_faces.push_back(mVolumep->getProfile().mFaces[face]); + } + + // Copy the old texture info off to the side, but not in the order in which + // they live in the mTextureList, rather in order of ther "face id" which + // is the corresponding value of LLVolueParams::LLProfile::mFaces::mIndex. + // + // Hence, some elements of old_tes::mEntryList will be invalid. It is + // initialized to a size of 9 (max number of possible faces on a volume?) + // and only the ones with valid types are filled in. + LLPrimTextureList old_tes; + old_tes.setSize(9); + for (face_bit = 0; face_bit < 9; face_bit++) + { + cur_mask = 0x1 << face_bit; + if (old_face_mask & cur_mask) + { + S32 te_index = face_index_from_id(cur_mask, old_faces); + old_tes.copyTexture(face_bit, *(getTE(te_index))); + //llinfos << face_bit << ":" << te_index << ":" << old_tes[face_bit].getID() << llendl; + } + } + + // build the new object sVolumeManager->unrefVolume(mVolumep); mVolumep = volumep; U32 new_face_mask = mVolumep->mFaceMask; - if (old_face_mask != new_face_mask) + S32 i; + + if (old_face_mask == new_face_mask) { + // nothing to do + return TRUE; + } + + if (mVolumep->getNumFaces() == 0 && new_face_mask != 0) + { + llwarns << "Object with 0 faces found...INCORRECT!" << llendl; setNumTEs(mVolumep->getNumFaces()); + return TRUE; + } + + // initialize face_mapping + S32 face_mapping[9]; + for (face_bit = 0; face_bit < 9; face_bit++) + { + face_mapping[face_bit] = face_bit; + } + + // The new shape may have more faces than the original, but we can't just + // add them to the end -- the ordering matters and it may be that we must + // insert the new faces in the middle of the list. When we add a face it + // will pick up the texture/color info of one of the old faces an so we + // now figure out which old face info gets mapped to each new face, and + // store in the face_mapping lookup table. + for (face_bit = 0; face_bit < 9; face_bit++) + { + cur_mask = 0x1 << face_bit; + if (!(new_face_mask & cur_mask)) + { + // Face doesn't exist in new map. + face_mapping[face_bit] = -1; + continue; + } + else if (old_face_mask & cur_mask) + { + // Face exists in new and old map. + face_mapping[face_bit] = face_bit; + continue; + } + + // OK, how we've got a mismatch, where we have to fill a new face with one from + // the old face. + if (cur_mask & (LL_FACE_PATH_BEGIN | LL_FACE_PATH_END | LL_FACE_INNER_SIDE)) + { + // It's a top/bottom/hollow interior face. + if (old_face_mask & LL_FACE_PATH_END) + { + face_mapping[face_bit] = 1; + continue; + } + else + { + S32 cur_outer_mask = LL_FACE_OUTER_SIDE_0; + for (i = 0; i < 4; i++) + { + if (old_face_mask & cur_outer_mask) + { + face_mapping[face_bit] = 5 + i; + break; + } + cur_outer_mask <<= 1; + } + if (i == 4) + { + llwarns << "No path end or outer face in volume!" << llendl; + } + continue; + } + } + + if (cur_mask & (LL_FACE_PROFILE_BEGIN | LL_FACE_PROFILE_END)) + { + // A cut slice. Use the hollow interior if we have it. + if (old_face_mask & LL_FACE_INNER_SIDE) + { + face_mapping[face_bit] = 2; + continue; + } + + // No interior, use the bottom face. + // Could figure out which of the outer faces was nearest, but that would be harder. + if (old_face_mask & LL_FACE_PATH_END) + { + face_mapping[face_bit] = 1; + continue; + } + else + { + S32 cur_outer_mask = LL_FACE_OUTER_SIDE_0; + for (i = 0; i < 4; i++) + { + if (old_face_mask & cur_outer_mask) + { + face_mapping[face_bit] = 5 + i; + break; + } + cur_outer_mask <<= 1; + } + if (i == 4) + { + llwarns << "No path end or outer face in volume!" << llendl; + } + continue; + } + } + + // OK, the face that's missing is an outer face... + // Pull from the nearest adjacent outer face (there's always guaranteed to be one... + S32 cur_outer = face_bit - 5; + S32 min_dist = 5; + S32 min_outer_bit = -1; + S32 i; + for (i = 0; i < 4; i++) + { + if (old_face_mask & (LL_FACE_OUTER_SIDE_0 << i)) + { + S32 dist = abs(i - cur_outer); + if (dist < min_dist) + { + min_dist = dist; + min_outer_bit = i + 5; + } + } + } + if (-1 == min_outer_bit) + { + llinfos << (LLVolume *)mVolumep << llendl; + llwarns << "Bad! No outer faces, impossible!" << llendl; + } + face_mapping[face_bit] = min_outer_bit; } + + setNumTEs(mVolumep->getNumFaces()); + for (face_bit = 0; face_bit < 9; face_bit++) + { + // For each possible face type on the new shape we check to see if that + // face exists and if it does we create a texture entry that is a copy + // of one of the originals. Since the originals might not have a + // matching face, we use the face_mapping lookup table to figure out + // which face information to copy. + cur_mask = 0x1 << face_bit; + if (new_face_mask & cur_mask) + { + if (-1 == face_mapping[face_bit]) + { + llwarns << "No mapping from old face to new face!" << llendl; + } + + S32 te_num = face_index_from_id(cur_mask, mVolumep->getProfile().mFaces); + setTE(te_num, *(old_tes.getTexture(face_mapping[face_bit]))); + } + } return TRUE; } -- cgit v1.3 From 04d2c459b9fe8859eaca0ebd5c3d44cd60dad334 Mon Sep 17 00:00:00 2001 From: brad kittenbrink Date: Tue, 1 Sep 2009 13:11:10 -0400 Subject: Fixup for closing paren dropped in the merge. --- indra/llmessage/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llmessage') diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 8e84e54ccf..571b2fc3c6 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -223,6 +223,8 @@ IF (NOT LINUX AND VIEWER) # llhttpclientadapter.cpp lltrustedmessageservice.cpp lltemplatemessagedispatcher.cpp + ) + # set(TEST_DEBUG on) set(test_libs ${LLMESSAGE_LIBRARIES} -- cgit v1.3 From 76d05a7e50bee33584f35f2f00f32ffe953752e6 Mon Sep 17 00:00:00 2001 From: Don Kjer Date: Fri, 4 Sep 2009 02:27:28 +0000 Subject: svn merge -r 129999:132607 svn+ssh://svn/svn/linden/branches/server/server-1.30 into trunk QAR-1863: Merge 525 changes from 1.27 -> 1.30 -> trunk 1.27 Changes: * QAR-1721: Vivox Recurring Billing (ViRB) * QAR-1743: 525 AO Classifieds * DEV-37701: Fix cleanup_indra nightly script so that it doesn't fail, and so that we know if it does * DEV-37926 - Fairmarket key roll 8/12/2009 * QAR-1744: 525 Landstore * QAR-1797: disable web services for find_people * QAR-1758: OpenID and Backup/recovery. * QAR-1794: ViRB proc fix - proc/recurring_billing/GetLocaleCountryByPackageId.php * Changing parameter name so the module signal could be used properly in the function below * QAR-1808 : Self-Reported First Name and Self-Reported Last Name have duplicate entries in the registration table * DEV-30204 : Moved the registration table from sequence to indra_aux. * removed dungeon from banned keyword list * DEV-30204 : Moved the registration table from sequence to indra_aux. Reviewed by kelly, cg, jarv * Merging new streambase dwell nightly to server-1.27 * Add update_parcel_dwell.py to serial_maintenance rule in the nightly.make Makefile, removing the dwell rule, since we don't want it running in parallel with other expensive nightly scripts. 1.30 Changes: * DEV-27476: version-manager only works in Firefox * DEV-27472: version-manager appears to load javascript from third party site. * mulib r92 that supports If-Modified-Since header and prevents arbitrary filesystem access through host:12107/manager/mochi/.. * Trivial conductor change that prevents a traceback when no regions are available to start. * Fixing "Out of Connections" error when running tests via SCUT * DEV-37554 - farm_deploy should get its ssh timeout from farm.ini * undo two lines of debugging changes that accidentally were checked in to farm * Adding MochiKit to MANIFEST for version-manager. * revised files for new parcel dwell process * Changing vault from an 'http' svn extern to a 'svn+ssh' svn extern. * DEV-38504 RPS map item queries are too verbose * added --skip-grid-mode-prompt to main in farm * DEV-38607: Region Presence Service includes disabled regions in estate responses, resulting in high slave query rate. * DEV-34781 - enable use_space_server_for_map_items and set map_item_request throttle settings in simulator.xml * DEV-38351 changes to ip_blacklist.xml file never get automatically picked up by the simulator * DEV-35443 Region border crossing issues. * DEV-35443 revert overzealous validity checking on neighbor messages. * DEV-15998 changing settings to re-enable teleport throttle, following DEV-37200 fix * QAR-1822 CPU Overrides update to indra.xml * DEV-15998 changing settings to re-enable teleport throttle, following DEV-37200 fix * DEV-34026 Update LLAgentInfo::forwardViewerStats() to make all viewer statistics * DEV-38906 Teleport throttle blocks prematurely when agent teleports back and forth between 2 regions * DEV-38861 Intra-region TP is blocked by inter-region TP throttl * DEV-38504 RPS map item queries are too verbose * initial import of deploy_to_dev_grid.sh --- indra/llinventory/lltransactiontypes.h | 12 ++++++++++++ indra/llmessage/llcurl.cpp | 13 +++++++++++-- indra/llmessage/llcurl.h | 6 ++++++ indra/llmessage/llhttpclient.cpp | 5 +++++ indra/llmessage/llregionpresenceverifier.cpp | 2 +- 5 files changed, 35 insertions(+), 3 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llinventory/lltransactiontypes.h b/indra/llinventory/lltransactiontypes.h index 1cb7308bd4..2c699bcb87 100644 --- a/indra/llinventory/lltransactiontypes.h +++ b/indra/llinventory/lltransactiontypes.h @@ -69,6 +69,12 @@ const S32 TRANS_PARCEL_DIR_FEE = 2003; const S32 TRANS_GROUP_TAX = 2004; // Taxes incurred as part of group membership const S32 TRANS_CLASSIFIED_RENEW = 2005; +// Codes 2100-2999 reserved for recurring billing services +// New codes can be created through an admin interface so may not +// automatically end up in the list below :-( +// So make sure you check the transaction_description table +const S32 TRANS_RECURRING_GENERIC = 2100; + // Codes 3000-3999 reserved for inventory transactions const S32 TRANS_GIVE_INVENTORY = 3000; @@ -84,6 +90,12 @@ const S32 TRANS_DWELL_BONUS = 5007; const S32 TRANS_PAY_OBJECT = 5008; const S32 TRANS_OBJECT_PAYS = 5009; +// Codes 5100-5999 reserved for recurring billing transfers between users +// New codes can be created through an admin interface so may not +// automatically end up in the list below :-( +// So make sure you check the transaction_description table +const S32 TRANS_RECURRING_GENERIC_USER = 5100; + // Codes 6000-6999 reserved for group transactions //const S32 TRANS_GROUP_JOIN = 6000; //reserved for future use const S32 TRANS_GROUP_LAND_DEED = 6001; diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 1a432cd7df..0d0fee6ee6 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -131,7 +131,7 @@ void LLCurl::Responder::error( // virtual void LLCurl::Responder::error(U32 status, const std::string& reason) { - llinfos << status << ": " << reason << llendl; + llinfos << mURL << " [" << status << "]: " << reason << llendl; } // virtual @@ -139,6 +139,11 @@ void LLCurl::Responder::result(const LLSD& content) { } +void LLCurl::Responder::setURL(const std::string& url) +{ + mURL = url; +} + // virtual void LLCurl::Responder::completedRaw( U32 status, @@ -148,7 +153,11 @@ void LLCurl::Responder::completedRaw( { LLSD content; LLBufferStream istr(channels, buffer.get()); - LLSDSerialize::fromXML(content, istr); + if (!LLSDSerialize::fromXML(content, istr)) + { + llinfos << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; + } + completed(status, reason, content); } diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index ff63904c91..6e45cbc8b5 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -120,8 +120,14 @@ public: // of the header can be parsed. In the ::completed call above only the body is contained in the LLSD. virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content); + // Used internally to set the url for debugging later. + void setURL(const std::string& url); + public: /* but not really -- don't touch this */ U32 mReferenceCount; + + private: + std::string mURL; }; typedef boost::intrusive_ptr ResponderPtr; diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 8b90a4c5ca..12ecbb36eb 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -265,6 +265,11 @@ static void request( } } + if (responder) + { + responder->setURL(url); + } + req->setCallback(new LLHTTPClientURLAdaptor(responder)); if (method == LLURLRequest::HTTP_POST && gMessageSystem) diff --git a/indra/llmessage/llregionpresenceverifier.cpp b/indra/llmessage/llregionpresenceverifier.cpp index 08c12f90da..e02a288473 100644 --- a/indra/llmessage/llregionpresenceverifier.cpp +++ b/indra/llmessage/llregionpresenceverifier.cpp @@ -67,7 +67,7 @@ void LLRegionPresenceVerifier::RegionResponder::result(const LLSD& content) LLHost destination(host, port); LLUUID id = content["region_id"]; - llinfos << "Verifying " << destination.getString() << " is region " << id << llendl; + lldebugs << "Verifying " << destination.getString() << " is region " << id << llendl; std::stringstream uri; uri << "http://" << destination.getString() << "/state/basic/"; -- cgit v1.3 From 74e584add651adbb5784276aba5770de9e295846 Mon Sep 17 00:00:00 2001 From: Christian Goetze Date: Fri, 4 Sep 2009 18:16:21 +0000 Subject: svn merge -r132607:132665 svn+ssh://svn.lindenlab.com/svn/linden/branches/server/server-1.30 Propagating to trunk: QAR-1705 DEV-29573 QAR-1859 QAR-1804 --- indra/llmessage/lltransfersourceasset.cpp | 1 - scripts/messages/message_template.msg | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'indra/llmessage') diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index 5a1cd95ffc..41f3f3f607 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -270,7 +270,6 @@ bool is_asset_fetch_by_id_allowed(LLAssetType::EType type) case LLAssetType::AT_BODYPART: case LLAssetType::AT_ANIMATION: case LLAssetType::AT_GESTURE: - case LLAssetType::AT_FAVORITE: rv = true; break; default: diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg index 9deab1f857..e1b01c569b 100644 --- a/scripts/messages/message_template.msg +++ b/scripts/messages/message_template.msg @@ -8950,6 +8950,7 @@ version 2.0 } } +// Link inventory { LinkInventoryItem Low 426 NotTrusted Zerocoded { @@ -8958,12 +8959,16 @@ version 2.0 { SessionID LLUUID } } { - InventoryData Variable + InventoryBlock Single { CallbackID U32 } // Async Response { FolderID LLUUID } + { TransactionID LLUUID } // Going to become TransactionID { OldItemID LLUUID } + { Type S8 } + { InvType S8 } { Name Variable 1 } - { AssetType U8 } + { Description Variable 1 } + } } -- cgit v1.3 From be7ac8bbfe991427636656ec1679b8119c9d9dbe Mon Sep 17 00:00:00 2001 From: Bryan O'Sullivan Date: Thu, 10 Sep 2009 10:31:58 -0700 Subject: Fix a link error with an integration test on OS X. --- .hgignore | 1 + indra/llmessage/CMakeLists.txt | 2 ++ 2 files changed, 3 insertions(+) (limited to 'indra/llmessage') diff --git a/.hgignore b/.hgignore index 61c38705f4..15fb913653 100644 --- a/.hgignore +++ b/.hgignore @@ -24,6 +24,7 @@ indra/newview/res-sdl indra/newview/skins indra/newview/vivox-runtime indra/server-linux-* +indra/test/linden_file.dat indra/test_apps/llmediatest/dependencies/i686-win32 indra/test_apps/terrain_mule/*.dll indra/viewer-linux-* diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index ec52179a39..453286b83d 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -3,6 +3,7 @@ project(llmessage) include(00-Common) +include(GoogleMock) include(LLAddBuildTest) include(LLCommon) include(LLMath) @@ -230,6 +231,7 @@ IF (NOT LINUX AND VIEWER) ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} ) LL_ADD_INTEGRATION_TEST( -- cgit v1.3 From d4b2897700c66354413af42ab055bd1aaa47f91c Mon Sep 17 00:00:00 2001 From: Rick Pasetto Date: Fri, 9 Oct 2009 18:56:36 -0700 Subject: Unit tests for LLMediaDataClient This required a bit of refactoring of LLMediaDataClient: - Created LLMediaDataClientObject ABC, which now has a concrete impl in LLVOVolume - Created unit test with 6 tests (for now), testing - LLObjectMediaDataClient::fetchMedia() - LLObjectMediaDataClient::updateMedia() - LLObjectMediaNavigateClient::navigate() - queue ordering - retries - nav bounce back - Also ensures that ref counting works properly (this is important, because ownership is tricky with smart pointers put into queues, peeled off into timers that fire and auto destruct, and HTTP responders that also auto-destruct) - Had to fix LLCurl::Responder's stub, which was not initializing the ref count to 0, causing the ref counting tests to fail (boy, that was hard to find!). Reviewed by Callum --- indra/llmessage/tests/llcurl_stub.cpp | 1 + indra/newview/CMakeLists.txt | 1 + indra/newview/app_settings/settings.xml | 11 + indra/newview/llmediadataclient.cpp | 135 ++++--- indra/newview/llmediadataclient.h | 85 ++++- indra/newview/llvovolume.cpp | 81 ++++- indra/newview/llvovolume.h | 1 + indra/newview/tests/llmediadataclient_test.cpp | 481 +++++++++++++++++++++++++ 8 files changed, 713 insertions(+), 83 deletions(-) create mode 100644 indra/newview/tests/llmediadataclient_test.cpp (limited to 'indra/llmessage') diff --git a/indra/llmessage/tests/llcurl_stub.cpp b/indra/llmessage/tests/llcurl_stub.cpp index 5dc5932fde..e6a5ad9946 100644 --- a/indra/llmessage/tests/llcurl_stub.cpp +++ b/indra/llmessage/tests/llcurl_stub.cpp @@ -22,6 +22,7 @@ #include "linden_common.h" LLCurl::Responder::Responder() + : mReferenceCount(0) { } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 9f0a0111f5..e0ca0a760f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1540,6 +1540,7 @@ include(LLAddBuildTest) SET(viewer_TEST_SOURCE_FILES llagentaccess.cpp lldateutil.cpp + llmediadataclient.cpp llviewerhelputil.cpp ) set_source_files_properties( diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 806f96a654..dc0e5ffa1d 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -5294,6 +5294,17 @@ Value 13 + PrimMediaMaxRetries + + Comment + Maximum number of retries for media queries. + Persist + 1 + Type + U32 + Value + 4 + PrimMediaRequestQueueDelay Comment diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index 4dde381e97..e69c85f245 100644 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -41,7 +41,6 @@ #include "llmediaentry.h" #include "lltextureentry.h" #include "llviewerregion.h" -#include "llvovolume.h" // // When making a request @@ -54,7 +53,9 @@ // - Any request that gets a 503 still goes through the retry logic // -// Some helpful logging macros +const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s) +const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs +const U32 LLMediaDataClient::MAX_RETRIES = 4; ////////////////////////////////////////////////////////////////////////////////////// // @@ -65,7 +66,7 @@ LLMediaDataClient::Request::Request(const std::string &cap_name, const LLSD& sd_payload, - LLVOVolume *obj, + LLMediaDataClientObject *obj, LLMediaDataClient *mdc) : mCapName(cap_name), mPayload(sd_payload), @@ -78,6 +79,7 @@ LLMediaDataClient::Request::Request(const std::string &cap_name, LLMediaDataClient::Request::~Request() { + LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL; mMDC = NULL; mObject = NULL; } @@ -85,7 +87,7 @@ LLMediaDataClient::Request::~Request() std::string LLMediaDataClient::Request::getCapability() const { - return getObject()->getRegion()->getCapability(getCapName()); + return getObject()->getCapabilityUrl(getCapName()); } // Helper function to get the "type" of request, which just pokes around to @@ -137,6 +139,17 @@ void LLMediaDataClient::Request::reEnqueue() const mMDC->enqueue(this); } +F32 LLMediaDataClient::Request::getRetryTimerDelay() const +{ + return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY : + mMDC->mRetryTimerDelay; +} + +U32 LLMediaDataClient::Request::getMaxNumRetries() const +{ + return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries; +} + std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) { s << "" @@ -163,6 +176,7 @@ LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr) // virtual LLMediaDataClient::Responder::RetryTimer::~RetryTimer() { + LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL; mResponder = NULL; } @@ -190,28 +204,31 @@ LLMediaDataClient::Responder::Responder(const request_ptr_t &request) LLMediaDataClient::Responder::~Responder() { + LL_DEBUGS("LLMediaDataClient") << "~Responder" << *(getRequest()) << LL_ENDL; mRequest = NULL; } /*virtual*/ void LLMediaDataClient::Responder::error(U32 status, const std::string& reason) { - extern LLControlGroup gSavedSettings; - if (status == HTTP_SERVICE_UNAVAILABLE) { - F32 retry_timeout = gSavedSettings.getF32("PrimMediaRetryTimerDelay"); - if (retry_timeout <= 0.0) - { - retry_timeout = (F32)UNAVAILABLE_RETRY_TIMER_DELAY; - } - LL_INFOS("LLMediaDataClient") << *mRequest << "got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; + F32 retry_timeout = mRequest->getRetryTimerDelay(); mRequest->incRetryCount(); - // Start timer (instances are automagically tracked by - // InstanceTracker<> and LLEventTimer) - new RetryTimer(F32(retry_timeout/*secs*/), this); + if (mRequest->getRetryCount() < mRequest->getMaxNumRetries()) + { + LL_INFOS("LLMediaDataClient") << *mRequest << "got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; + + // Start timer (instances are automagically tracked by + // InstanceTracker<> and LLEventTimer) + new RetryTimer(F32(retry_timeout/*secs*/), this); + } + else { + LL_INFOS("LLMediaDataClient") << *mRequest << "got SERVICE_UNAVAILABLE...retry count " << + mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; + } } else { std::string msg = boost::lexical_cast(status) + ": " + reason; @@ -252,29 +269,25 @@ bool LLMediaDataClient::Comparator::operator() (const request_ptr_t &o1, const r // Calculate the scores for each. F64 o1_score = Comparator::getObjectScore(o1->getObject()); F64 o2_score = Comparator::getObjectScore(o2->getObject()); - - return ( o1_score > o2_score ); + + // XXX Weird: a higher score should go earlier, but by observation I notice + // that this causes further-away objects load first. This is counterintuitive + // to the priority_queue Comparator, which states that this function should + // return 'true' if o1 should be *before* o2. + // In other words, I'd have expected that the following should return + // ( o1_score > o2_score). + return ( o1_score < o2_score ); } // static -F64 LLMediaDataClient::Comparator::getObjectScore(const ll_vo_volume_ptr_t &obj) +F64 LLMediaDataClient::Comparator::getObjectScore(const LLMediaDataClientObject::ptr_t &obj) { // *TODO: make this less expensive? - F32 dist = obj->getRenderPosition().length() + 0.1; // avoids div by 0 + F64 dist = obj->getDistanceFromAvatar() + 0.1; // avoids div by 0 // square the distance so that they are in the same "unit magnitude" as // the interest (which is an area) dist *= dist; - F64 interest = (F64)1; - int i = 0; - int end = obj->getNumTEs(); - for ( ; i < end; ++i) - { - const viewer_media_t &impl = obj->getMediaImpl(i); - if (!impl.isNull()) - { - interest += impl->getInterest(); - } - } + F64 interest = obj->getTotalMediaInterest() + 1.0; return interest/dist; } @@ -282,7 +295,7 @@ F64 LLMediaDataClient::Comparator::getObjectScore(const ll_vo_volume_ptr_t &obj) ////////////////////////////////////////////////////////////////////////////////////// // // LLMediaDataClient::PriorityQueue -// Queue of LLVOVolume smart pointers to request media for. +// Queue of LLMediaDataClientObject smart pointers to request media for. // ////////////////////////////////////////////////////////////////////////////////////// @@ -304,7 +317,7 @@ std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::PriorityQueue ////////////////////////////////////////////////////////////////////////////////////// // // LLMediaDataClient::QueueTimer -// Queue of LLVOVolume smart pointers to request media for. +// Queue of LLMediaDataClientObject smart pointers to request media for. // ////////////////////////////////////////////////////////////////////////////////////// @@ -316,6 +329,7 @@ LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc) LLMediaDataClient::QueueTimer::~QueueTimer() { + LL_DEBUGS("LLMediaDataClient") << "~QueueTimer" << LL_ENDL; mMDC->setIsRunning(false); mMDC = NULL; } @@ -343,10 +357,10 @@ BOOL LLMediaDataClient::QueueTimer::tick() // Peel one off of the items from the queue, and execute request request_ptr_t request = queue.top(); llassert(!request.isNull()); - const ll_vo_volume_ptr_t &object = request->getObject(); + const LLMediaDataClientObject *object = request->getObject(); bool performed_request = false; - llassert(!object.isNull()); - if (!object.isNull() && object->hasMedia()) + llassert(NULL != object); + if (NULL != object && object->hasMedia()) { std::string url = request->getCapability(); if (!url.empty()) @@ -368,12 +382,13 @@ BOOL LLMediaDataClient::QueueTimer::tick() LL_INFOS("LLMediaDataClient") << "Not Sending request for " << *request << " hasMedia() is false!" << LL_ENDL; } } - bool exceeded_retries = request->getRetryCount() > LLMediaDataClient::MAX_RETRIES; + bool exceeded_retries = request->getRetryCount() > mMDC->mMaxNumRetries; if (performed_request || exceeded_retries) // Try N times before giving up { if (exceeded_retries) { - LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << LLMediaDataClient::MAX_RETRIES << " tries...popping object id " << object->getID() << LL_ENDL; + LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " + << mMDC->mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL; // XXX Should we bring up a warning dialog?? } queue.pop(); @@ -390,15 +405,9 @@ void LLMediaDataClient::startQueueTimer() { if (! mQueueTimerIsRunning) { - extern LLControlGroup gSavedSettings; - F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay"); - if (queue_timer_delay <= 0.0f) - { - queue_timer_delay = (F32)LLMediaDataClient::QUEUE_TIMER_DELAY; - } - LL_INFOS("LLMediaDataClient") << "starting queue timer (delay=" << queue_timer_delay << " seconds)" << LL_ENDL; + LL_INFOS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL; // LLEventTimer automagically takes care of the lifetime of this object - new QueueTimer(queue_timer_delay, this); + new QueueTimer(mQueueTimerDelay, this); } } @@ -407,9 +416,9 @@ void LLMediaDataClient::stopQueueTimer() mQueueTimerIsRunning = false; } -void LLMediaDataClient::request(LLVOVolume *object, const LLSD &payload) +void LLMediaDataClient::request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload) { - if (NULL == object || ! object->hasMedia()) return; + if (object.isNull() || ! object->hasMedia()) return; // Push the object on the priority queue enqueue(new Request(getCapabilityName(), payload, object, this)); @@ -432,7 +441,13 @@ void LLMediaDataClient::enqueue(const Request *request) // ////////////////////////////////////////////////////////////////////////////////////// -LLMediaDataClient::LLMediaDataClient() +LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, + F32 retry_timer_delay, + U32 max_retries) + : mQueueTimerDelay(queue_timer_delay), + mRetryTimerDelay(retry_timer_delay), + mMaxNumRetries(max_retries), + mQueueTimerIsRunning(false) { pRequestQueue = new PriorityQueue(); } @@ -442,12 +457,17 @@ LLMediaDataClient::~LLMediaDataClient() stopQueueTimer(); // This should clear the queue, and hopefully call all the destructors. - LL_DEBUGS("LLMediaDataClient") << "destructor: queue: " << + LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClient destructor: queue: " << (pRequestQueue->empty() ? " " : " ") << (*pRequestQueue) << LL_ENDL; delete pRequestQueue; pRequestQueue = NULL; } +bool LLMediaDataClient::isEmpty() const +{ + return (NULL == pRequestQueue) ? true : pRequestQueue->empty(); +} + ////////////////////////////////////////////////////////////////////////////////////// // // LLObjectMediaDataClient @@ -465,7 +485,7 @@ const char *LLObjectMediaDataClient::getCapabilityName() const return "ObjectMedia"; } -void LLObjectMediaDataClient::fetchMedia(LLVOVolume *object) +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) { LLSD sd_payload; sd_payload["verb"] = "GET"; @@ -473,18 +493,17 @@ void LLObjectMediaDataClient::fetchMedia(LLVOVolume *object) request(object, sd_payload); } -void LLObjectMediaDataClient::updateMedia(LLVOVolume *object) +void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) { LLSD sd_payload; sd_payload["verb"] = "UPDATE"; sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); LLSD object_media_data; - for (int i=0; i < object->getNumTEs(); i++) { - LLTextureEntry *texture_entry = object->getTE(i); - llassert((texture_entry->getMediaData() != NULL) == texture_entry->hasMedia()); - const LLSD &media_data = - (texture_entry->getMediaData() == NULL) ? LLSD() : texture_entry->getMediaData()->asLLSD(); - object_media_data.append(media_data); + int i = 0; + int end = object->getMediaDataCount(); + for ( ; i < end ; ++i) + { + object_media_data.append(object->getMediaDataLLSD(i)); } sd_payload[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; @@ -548,7 +567,7 @@ const char *LLObjectMediaNavigateClient::getCapabilityName() const return "ObjectMediaNavigate"; } -void LLObjectMediaNavigateClient::navigate(LLVOVolume *object, U8 texture_index, const std::string &url) +void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) { LLSD sd_payload; sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h index 949e7239be..9d0aa0981e 100644 --- a/indra/newview/llmediadataclient.h +++ b/indra/newview/llmediadataclient.h @@ -36,12 +36,36 @@ #include "llhttpclient.h" #include #include "llrefcount.h" +#include "llpointer.h" #include "lltimer.h" -// Forward decls -class LLVOVolume; -typedef LLPointer ll_vo_volume_ptr_t; +// Link seam for LLVOVolume +class LLMediaDataClientObject : public LLRefCount +{ +public: + // Get the number of media data items + virtual U8 getMediaDataCount() const = 0; + // Get the media data at index, as an LLSD + virtual LLSD getMediaDataLLSD(U8 index) const = 0; + // Get this object's UUID + virtual LLUUID getID() const = 0; + // Navigate back to previous URL + virtual void mediaNavigateBounceBack(U8 index) = 0; + // Does this object have media? + virtual bool hasMedia() const = 0; + // Update the object's media data to the given array + virtual void updateObjectMediaData(LLSD const &media_data_array) = 0; + // Return the distance from the object to the avatar + virtual F64 getDistanceFromAvatar() const = 0; + // Return the total "interest" of the media (on-screen area) + virtual F64 getTotalMediaInterest() const = 0; + // Return the given cap url + virtual std::string getCapabilityUrl(const std::string &name) const = 0; + + // smart pointer + typedef LLPointer ptr_t; +}; // This object creates a priority queue for requests. // Abstracts the Cap URL, the request, and the responder @@ -50,15 +74,23 @@ class LLMediaDataClient : public LLRefCount public: LOG_CLASS(LLMediaDataClient); - const static int QUEUE_TIMER_DELAY = 1; // seconds(s) - const static int MAX_RETRIES = 4; + const static F32 QUEUE_TIMER_DELAY;// = 1.0; // seconds(s) + const static F32 UNAVAILABLE_RETRY_TIMER_DELAY;// = 5.0; // secs + const static U32 MAX_RETRIES;// = 4; // Constructor - LLMediaDataClient(); + LLMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES); // Make the request - void request(LLVOVolume *object, const LLSD &payload); - + void request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload); + + F32 getRetryTimerDelay() const { return mRetryTimerDelay; } + + // Returns true iff the queue is empty + bool isEmpty() const; + protected: // Destructor virtual ~LLMediaDataClient(); // use unref @@ -73,10 +105,10 @@ protected: NAVIGATE }; - Request(const std::string &cap_name, const LLSD& sd_payload, LLVOVolume *obj, LLMediaDataClient *mdc); + Request(const std::string &cap_name, const LLSD& sd_payload, LLMediaDataClientObject *obj, LLMediaDataClient *mdc); const std::string &getCapName() const { return mCapName; } const LLSD &getPayload() const { return mPayload; } - LLVOVolume *getObject() const { return mObject; } + LLMediaDataClientObject *getObject() const { return mObject; } U32 getNum() const { return mNum; } @@ -92,6 +124,9 @@ protected: // Re-enqueue thyself void reEnqueue() const; + F32 getRetryTimerDelay() const; + U32 getMaxNumRetries() const; + public: friend std::ostream& operator<<(std::ostream &s, const Request &q); @@ -101,7 +136,7 @@ protected: private: std::string mCapName; LLSD mPayload; - ll_vo_volume_ptr_t mObject; + LLMediaDataClientObject::ptr_t mObject; // Simple tracking const U32 mNum; static U32 sNum; @@ -115,8 +150,6 @@ protected: // Responder class Responder : public LLHTTPClient::Responder { - static const int UNAVAILABLE_RETRY_TIMER_DELAY = 5; // secs - public: Responder(const request_ptr_t &request); //If we get back an error (not found, etc...), handle it here @@ -163,7 +196,7 @@ private: public: bool operator() (const request_ptr_t &o1, const request_ptr_t &o2) const; private: - static F64 getObjectScore(const ll_vo_volume_ptr_t &obj); + static F64 getObjectScore(const LLMediaDataClientObject::ptr_t &obj); }; // PriorityQueue @@ -194,6 +227,10 @@ private: void startQueueTimer(); void stopQueueTimer(); void setIsRunning(bool val) { mQueueTimerIsRunning = val; } + + const F32 mQueueTimerDelay; + const F32 mRetryTimerDelay; + const U32 mMaxNumRetries; bool mQueueTimerIsRunning; @@ -205,11 +242,15 @@ private: class LLObjectMediaDataClient : public LLMediaDataClient { public: - LLObjectMediaDataClient() {} + LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES) + : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) + {} ~LLObjectMediaDataClient() {} - void fetchMedia(LLVOVolume *object); - void updateMedia(LLVOVolume *object); + void fetchMedia(LLMediaDataClientObject *object); + void updateMedia(LLMediaDataClientObject *object); protected: // Subclasses must override this factory method to return a new responder @@ -231,14 +272,18 @@ protected: // MediaDataResponder specific for the ObjectMediaNavigate cap class LLObjectMediaNavigateClient : public LLMediaDataClient { +public: // NOTE: from llmediaservice.h static const int ERROR_PERMISSION_DENIED_CODE = 8002; -public: - LLObjectMediaNavigateClient() {} + LLObjectMediaNavigateClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES) + : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) + {} ~LLObjectMediaNavigateClient() {} - void navigate(LLVOVolume *object, U8 texture_index, const std::string &url); + void navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url); protected: // Subclasses must override this factory method to return a new responder diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 83e65af054..1704f63376 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -91,6 +91,57 @@ LLPointer LLVOVolume::sObjectMediaNavigateClient = static LLFastTimer::DeclareTimer FTM_GEN_TRIANGLES("Generate Triangles"); static LLFastTimer::DeclareTimer FTM_GEN_VOLUME("Generate Volumes"); +// Implementation class of LLMediaDataClientObject. See llmediadataclient.h +class LLMediaDataClientObjectImpl : public LLMediaDataClientObject +{ +public: + LLMediaDataClientObjectImpl(LLVOVolume *obj) : mObject(obj) {} + LLMediaDataClientObjectImpl() { mObject = NULL; } + + virtual U8 getMediaDataCount() const + { return mObject->getNumTEs(); } + + virtual LLSD getMediaDataLLSD(U8 index) const + { + LLSD result; + LLTextureEntry *te = mObject->getTE(index); + if (NULL != te) + { + llassert((te->getMediaData() != NULL) == te->hasMedia()); + if (te->getMediaData() != NULL) + { + result = te->getMediaData()->asLLSD(); + } + } + return result; + } + + virtual LLUUID getID() const + { return mObject->getID(); } + + virtual void mediaNavigateBounceBack(U8 index) + { mObject->mediaNavigateBounceBack(index); } + + virtual bool hasMedia() const + { return mObject->hasMedia(); } + + virtual void updateObjectMediaData(LLSD const &data) + { mObject->updateObjectMediaData(data); } + + virtual F64 getDistanceFromAvatar() const + { return mObject->getRenderPosition().length(); } + + virtual F64 getTotalMediaInterest() const + { return mObject->getTotalMediaInterest(); } + + virtual std::string getCapabilityUrl(const std::string &name) const + { return mObject->getRegion()->getCapability(name); } + +private: + LLPointer mObject; +}; + + LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : LLViewerObject(id, pcode, regionp), mVolumeImpl(NULL) @@ -134,8 +185,12 @@ LLVOVolume::~LLVOVolume() // static void LLVOVolume::initClass() { - sObjectMediaClient = new LLObjectMediaDataClient(); - sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(); + // gSavedSettings better be around + const F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay"); + const F32 retry_timer_delay = gSavedSettings.getF32("PrimMediaRetryTimerDelay"); + const U32 max_retries = gSavedSettings.getU32("PrimMediaMaxRetries"); + sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries); + sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay, max_retries); } // static @@ -1634,7 +1689,7 @@ bool LLVOVolume::hasMedia() const void LLVOVolume::requestMediaDataUpdate() { - sObjectMediaClient->fetchMedia(this); + sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this)); } void LLVOVolume::cleanUpMediaImpls() @@ -1834,7 +1889,7 @@ void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, llinfos << "broadcasting navigate with URI " << new_location << llendl; - sObjectMediaNavigateClient->navigate(this, face_index, new_location); + sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this), face_index, new_location); } } break; @@ -1860,7 +1915,7 @@ void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, void LLVOVolume::sendMediaDataUpdate() { - sObjectMediaClient->updateMedia(this); + sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this)); } void LLVOVolume::removeMediaImpl(S32 texture_index) @@ -1953,6 +2008,22 @@ viewer_media_t LLVOVolume::getMediaImpl(U8 face_id) const return NULL; } +F64 LLVOVolume::getTotalMediaInterest() const +{ + F64 interest = (F64)0.0; + int i = 0; + const int end = getNumTEs(); + for ( ; i < end; ++i) + { + const viewer_media_t &impl = getMediaImpl(i); + if (!impl.isNull()) + { + interest += impl->getInterest(); + } + } + return interest; +} + S32 LLVOVolume::getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id) { S32 end = (S32)mMediaImplList.size() ; diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index bb2b890000..90dfa2204b 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -255,6 +255,7 @@ public: viewer_media_t getMediaImpl(U8 face_id) const; S32 getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id); + F64 getTotalMediaInterest() const; bool hasMedia() const; diff --git a/indra/newview/tests/llmediadataclient_test.cpp b/indra/newview/tests/llmediadataclient_test.cpp new file mode 100644 index 0000000000..135c5ab501 --- /dev/null +++ b/indra/newview/tests/llmediadataclient_test.cpp @@ -0,0 +1,481 @@ +/** + * @file llmediadataclient_test.cpp + * @brief LLMediaDatClient tests + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include +#include "../test/lltut.h" + +#include "llsdserialize.h" +#include "llerrorcontrol.h" +#include "llhttpstatuscodes.h" + +#include "../llmediadataclient.h" +#include "../llvovolume.h" + +#include "../../llprimitive/llmediaentry.cpp" +#include "../../llprimitive/lltextureentry.cpp" +#include "../../llmessage/tests/llcurl_stub.cpp" + +#include + +#define VALID_OBJECT_ID "3607d5c4-644b-4a8a-871a-8b78471af2a2" +#define VALID_OBJECT_ID_1 "11111111-1111-1111-1111-111111111111" +#define VALID_OBJECT_ID_2 "22222222-2222-2222-2222-222222222222" +#define VALID_OBJECT_ID_3 "33333333-3333-3333-3333-333333333333" +#define VALID_OBJECT_ID_4 "44444444-4444-4444-4444-444444444444" + +#define FAKE_OBJECT_MEDIA_CAP_URL "foo_ObjectMedia" +#define FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL "foo_ObjectMediaNavigate" +#define FAKE_OBJECT_MEDIA_CAP_URL_503 "foo_ObjectMedia_503" +#define FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR "foo_ObjectMediaNavigate_ERROR" + +#define MEDIA_DATA "\ + \ +foo \ +bar \ +baz \ +" + +#define _DATA_URLS(ID,DIST,INT,URL1,URL2) " \ + \ + \ + uuid \ + " ID " \ + distance \ + " DIST " \ + interest \ + " INT " \ + cap_urls \ + \ + ObjectMedia \ + " URL1 " \ + ObjectMediaNavigate \ + " URL2 " \ + \ + media_data \ + " MEDIA_DATA " \ + \ +" + +#define _DATA(ID,DIST,INT) _DATA_URLS(ID,DIST,INT,FAKE_OBJECT_MEDIA_CAP_URL,FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL) + +const char *DATA = _DATA(VALID_OBJECT_ID,"1.0","1.0"); + +#define STR(I) boost::lexical_cast(I) + +#define LOG_TEST(N) LL_DEBUGS("LLMediaDataClient") << "\n" << \ +"================================================================================\n" << \ +"===================================== TEST " #N " ===================================\n" << \ +"================================================================================\n" << LL_ENDL; + +LLSD *gPostRecords = NULL; + +// stubs: +void LLHTTPClient::post( + const std::string& url, + const LLSD& body, + LLHTTPClient::ResponderPtr responder, + const LLSD& headers, + const F32 timeout) +{ + LLSD record; + record["url"] = url; + record["body"] = body; + record["headers"] = headers; + record["timeout"] = timeout; + gPostRecords->append(record); + + // Magic URL that triggers a 503: + if ( url == FAKE_OBJECT_MEDIA_CAP_URL_503 ) + { + responder->error(HTTP_SERVICE_UNAVAILABLE, "fake reason"); + } + else if (url == FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR) + { + LLSD result; + LLSD error; + error["code"] = LLObjectMediaNavigateClient::ERROR_PERMISSION_DENIED_CODE; + result["error"] = error; + responder->result(result); + } + else { + responder->result(LLSD()); + } +} + +const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; + +class LLMediaDataClientObjectTest : public LLMediaDataClientObject +{ +public: + LLMediaDataClientObjectTest(const char *data) + { + std::istringstream d(data); + LLSDSerialize::fromXML(mRep, d); + mNumBounceBacks = 0; + + // std::cout << ll_pretty_print_sd(mRep) << std::endl; + // std::cout << "ID: " << getID() << std::endl; + } + LLMediaDataClientObjectTest(const LLSD &rep) + : mRep(rep), mNumBounceBacks(0) {} + ~LLMediaDataClientObjectTest() + { LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClientObjectTest" << LL_ENDL; } + + virtual U8 getMediaDataCount() const + { return mRep["media_data"].size(); } + virtual LLSD getMediaDataLLSD(U8 index) const + { return mRep["media_data"][(LLSD::Integer)index]; } + virtual LLUUID getID() const + { return mRep["uuid"]; } + virtual void mediaNavigateBounceBack(U8 index) + { + mNumBounceBacks++; + } + + virtual bool hasMedia() const + { return mRep.has("media_data"); } + + virtual void updateObjectMediaData(LLSD const &media_data_array) + { mRep["media_data"] = media_data_array; } + + virtual F64 getDistanceFromAvatar() const + { return (LLSD::Real)mRep["distance"]; } + + virtual F64 getTotalMediaInterest() const + { return (LLSD::Real)mRep["interest"]; } + + virtual std::string getCapabilityUrl(const std::string &name) const + { return mRep["cap_urls"][name]; } + + int getNumBounceBacks() const + { return mNumBounceBacks; } + +private: + LLSD mRep; + int mNumBounceBacks; +}; + + +namespace tut +{ + struct mediadataclient + { + mediadataclient() { + gPostRecords = &mLLSD; + +// LLError::setDefaultLevel(LLError::LEVEL_DEBUG); +// LLError::setClassLevel("LLMediaDataClient", LLError::LEVEL_DEBUG); +// LLError::setTagLevel("MediaOnAPrim", LLError::LEVEL_DEBUG); + } + LLSD mLLSD; + }; + + typedef test_group mediadataclient_t; + typedef mediadataclient_t::object mediadataclient_object_t; + tut::mediadataclient_t tut_mediadataclient("mediadataclient"); + + void ensure(const std::string &msg, int value, int expected) + { + std::string m = msg; + m += " value: " + STR(value); + m += ", expected: " + STR(expected); + ensure(m, value == expected); + } + + void ensure(const std::string &msg, const std::string & value, const std::string & expected) + { + std::string m = msg; + m += " value: " + value; + m += ", expected: " + expected; + ensure(m, value == expected); + } + + void ensure(const std::string &msg, const LLUUID & value, const LLUUID & expected) + { + std::string m = msg; + m += " value: " + value.asString(); + m += ", expected: " + expected.asString(); + ensure(m, value == expected); + } + + void ensure_llsd(const std::string &msg, const LLSD & value, const char *expected) + { + LLSD expected_llsd; + std::istringstream e(expected); + LLSDSerialize::fromXML(expected_llsd, e); + + std::string value_str = ll_pretty_print_sd(value); + std::string expected_str = ll_pretty_print_sd(expected_llsd); + std::string m = msg; + m += " value: " + value_str; + m += ", expected: " + expected_str; + ensure(m, value_str == expected_str); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + template<> template<> + void mediadataclient_object_t::test<1>() + { + // + // Test fetchMedia() + // + LOG_TEST(1); + + LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); + int num_refs_start = o->getNumRefs(); + { + // queue time w/ no delay ensures that LLEventTimer::updateClass() will hit the tick() + LLPointer mdc = new LLObjectMediaDataClient(0,0,4); + mdc->fetchMedia(o); + + // Make sure no posts happened yet... + ensure("post records", gPostRecords->size(), 0); + + LLEventTimer::updateClass(); + + ensure("post records", gPostRecords->size(), 1); + ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_CAP_URL); + ensure("post GET", (*gPostRecords)[0]["body"]["verb"], "GET"); + ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); + ensure("queue empty", mdc->isEmpty()); + } + + // Make sure everyone's destroyed properly + ensure("REF COUNT", o->getNumRefs(), num_refs_start); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + template<> template<> + void mediadataclient_object_t::test<2>() + { + // + // Test updateMedia() + // + LOG_TEST(2); + + LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); + { + // queue time w/ no delay ensures that LLEventTimer::updateClass() will hit the tick() + LLPointer mdc = new LLObjectMediaDataClient(0,0,4); + mdc->updateMedia(o); + ensure("post records", gPostRecords->size(), 0); + LLEventTimer::updateClass(); + + ensure("post records", gPostRecords->size(), 1); + ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_CAP_URL); + ensure("post UPDATE", (*gPostRecords)[0]["body"]["verb"], "UPDATE"); + ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); + ensure_llsd("post data llsd", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_MEDIA_DATA_KEY], + "" MEDIA_DATA ""); + ensure("queue empty", mdc->isEmpty()); + } + + ensure("REF COUNT", o->getNumRefs(), 1); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + template<> template<> + void mediadataclient_object_t::test<3>() + { + // + // Test navigate() + // + LOG_TEST(3); + + LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest(DATA); + { + LLPointer mdc = new LLObjectMediaNavigateClient(0,0,4); + const char *TEST_URL = "http://example.com"; + mdc->navigate(o, 0, TEST_URL); + ensure("post records", gPostRecords->size(), 0); + LLEventTimer::updateClass(); + + // ensure no bounce back + ensure("bounce back", dynamic_cast(static_cast(o))->getNumBounceBacks(), 0); + + ensure("post records", gPostRecords->size(), 1); + ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL); + ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); + ensure("post data", (*gPostRecords)[0]["body"][LLTextureEntry::TEXTURE_INDEX_KEY], 0); + ensure("post data", (*gPostRecords)[0]["body"][LLMediaEntry::CURRENT_URL_KEY], TEST_URL); + ensure("queue empty", mdc->isEmpty()); + } + ensure("REF COUNT", o->getNumRefs(), 1); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + template<> template<> + void mediadataclient_object_t::test<4>() + { + // + // Test queue ordering + // + LOG_TEST(4); + + LLMediaDataClientObject::ptr_t o1 = new LLMediaDataClientObjectTest( + _DATA(VALID_OBJECT_ID_1,"3.0","1.0")); + LLMediaDataClientObject::ptr_t o2 = new LLMediaDataClientObjectTest( + _DATA(VALID_OBJECT_ID_2,"1.0","1.0")); + LLMediaDataClientObject::ptr_t o3 = new LLMediaDataClientObjectTest( + _DATA(VALID_OBJECT_ID_3,"2.0","1.0")); + { + LLPointer mdc = new LLObjectMediaDataClient(0,0,4); + const char *ORDERED_OBJECT_IDS[] = { VALID_OBJECT_ID_2, VALID_OBJECT_ID_3, VALID_OBJECT_ID_1 }; + mdc->fetchMedia(o1); + mdc->fetchMedia(o2); + mdc->fetchMedia(o3); + + // Make sure no posts happened yet... + ensure("post records", gPostRecords->size(), 0); + + // tick 3 times... + LLEventTimer::updateClass(); + ensure("post records", gPostRecords->size(), 1); + LLEventTimer::updateClass(); + ensure("post records", gPostRecords->size(), 2); + LLEventTimer::updateClass(); + ensure("post records", gPostRecords->size(), 3); + + for( int i=0; i < 3; i++ ) + { + ensure("[" + STR(i) + "] post url", (*gPostRecords)[i]["url"], FAKE_OBJECT_MEDIA_CAP_URL); + ensure("[" + STR(i) + "] post GET", (*gPostRecords)[i]["body"]["verb"], "GET"); + ensure("[" + STR(i) + "] post object id", (*gPostRecords)[i]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), + LLUUID(ORDERED_OBJECT_IDS[i])); + } + + ensure("queue empty", mdc->isEmpty()); + } + ensure("refcount of o1", o1->getNumRefs(), 1); + ensure("refcount of o2", o2->getNumRefs(), 1); + ensure("refcount of o3", o3->getNumRefs(), 1); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + + template<> template<> + void mediadataclient_object_t::test<5>() + { + // + // Test fetchMedia() getting a 503 error + // + LOG_TEST(5); + + LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest( + _DATA_URLS(VALID_OBJECT_ID, + "1.0", + "1.0", + FAKE_OBJECT_MEDIA_CAP_URL_503, + FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL)); + int num_refs_start = o->getNumRefs(); + { + const int NUM_RETRIES = 5; + LLPointer mdc = new LLObjectMediaDataClient(0,0,NUM_RETRIES); + + // This should generate a retry + mdc->fetchMedia(o); + + // Make sure no posts happened yet... + ensure("post records before", gPostRecords->size(), 0); + + // Once, causes retry + // Second, fires retry timer + // Third, fires queue timer again + for (int i=0; isize(), i+1); + LLEventTimer::updateClass(); + } + + // Do some extre pumps to make sure no other timer work occurs. + LLEventTimer::updateClass(); + LLEventTimer::updateClass(); + LLEventTimer::updateClass(); + + // Make sure there were 2 posts + ensure("post records after", gPostRecords->size(), NUM_RETRIES); + for (int i=0; iisEmpty()); + } + + // Make sure everyone's destroyed properly + ensure("REF COUNT", o->getNumRefs(), num_refs_start); + } + + template<> template<> + void mediadataclient_object_t::test<6>() + { + // + // Test navigate() with a bounce back + // + LOG_TEST(6); + + LLMediaDataClientObject::ptr_t o = new LLMediaDataClientObjectTest( + _DATA_URLS(VALID_OBJECT_ID, + "1.0", + "1.0", + FAKE_OBJECT_MEDIA_CAP_URL, + FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR)); + { + LLPointer mdc = new LLObjectMediaNavigateClient(0,0,4); + const char *TEST_URL = "http://example.com"; + mdc->navigate(o, 0, TEST_URL); + ensure("post records", gPostRecords->size(), 0); + LLEventTimer::updateClass(); + + // ensure bounce back + ensure("bounce back", + dynamic_cast(static_cast(o))->getNumBounceBacks(), + 1); + + ensure("post records", gPostRecords->size(), 1); + ensure("post url", (*gPostRecords)[0]["url"], FAKE_OBJECT_MEDIA_NAVIGATE_CAP_URL_ERROR); + ensure("post object id", (*gPostRecords)[0]["body"][LLTextureEntry::OBJECT_ID_KEY].asUUID(), LLUUID(VALID_OBJECT_ID)); + ensure("post data", (*gPostRecords)[0]["body"][LLTextureEntry::TEXTURE_INDEX_KEY], 0); + ensure("post data", (*gPostRecords)[0]["body"][LLMediaEntry::CURRENT_URL_KEY], TEST_URL); + ensure("queue empty", mdc->isEmpty()); + } + ensure("REF COUNT", o->getNumRefs(), 1); + } + + +} -- cgit v1.3