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/test/CMakeLists.txt | 6 +- indra/test/lldoubledispatch_tut.cpp | 245 ++++++++++++++++++++++++++++++++++++ indra/test/llpermissions_tut.cpp | 11 +- indra/test/llsaleinfo_tut.cpp | 15 +-- indra/test/llscriptresource_tut.cpp | 7 +- indra/test/lltimestampcache_tut.cpp | 7 +- indra/test/lltranscode_tut.cpp | 7 +- indra/test/test.h | 5 +- 8 files changed, 269 insertions(+), 34 deletions(-) create mode 100755 indra/test/lldoubledispatch_tut.cpp (limited to 'indra/test') diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index c8682c8ea7..c74ef06636 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -34,6 +34,7 @@ set(test_SOURCE_FILES llblowfish_tut.cpp llbuffer_tut.cpp lldate_tut.cpp + lldoubledispatch_tut.cpp llerror_tut.cpp llhost_tut.cpp llhttpdate_tut.cpp @@ -64,6 +65,7 @@ set(test_SOURCE_FILES lltimestampcache_tut.cpp lltiming_tut.cpp lltranscode_tut.cpp + lltreeiterators_tut.cpp lltut.cpp lluri_tut.cpp lluuidhashmap_tut.cpp @@ -104,7 +106,7 @@ endif (NOT DARWIN) set_source_files_properties(${test_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) -list(APPEND test_SOURC_FILES ${test_HEADER_FILES}) +list(APPEND test_SOURCE_FILES ${test_HEADER_FILES}) add_executable(test ${test_SOURCE_FILES}) @@ -120,6 +122,8 @@ target_link_libraries(test ${APRICONV_LIBRARIES} ${PTHREAD_LIBRARY} ${WINDOWS_LIBRARIES} + ${BOOST_PROGRAM_OPTIONS_LIBRARY} + ${BOOST_REGEX_LIBRARY} ${DL_LIBRARY} ) diff --git a/indra/test/lldoubledispatch_tut.cpp b/indra/test/lldoubledispatch_tut.cpp new file mode 100755 index 0000000000..63ef4d4497 --- /dev/null +++ b/indra/test/lldoubledispatch_tut.cpp @@ -0,0 +1,245 @@ +/** + * @file lldoubledispatch_tut.cpp + * @author Nat Goodspeed + * @date 2008-11-13 + * @brief Test for lldoubledispatch.h + * + * This program tests the DoubleDispatch class, using a variation on the example + * from Scott Meyers' "More Effective C++", Item 31. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * 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 + * 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$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lldoubledispatch.h" +// STL headers +// std headers +#include +#include +#include +// external library headers +// other Linden headers +#include "lltut.h" + + +/*---------------------------- Class hierarchy -----------------------------*/ +// All objects are GameObjects. +class GameObject +{ +public: + GameObject(const std::string& name): mName(name) {} + virtual ~GameObject() {} + virtual std::string stringize() { return std::string(typeid(*this).name()) + " " + mName; } + +protected: + std::string mName; +}; + +// SpaceStation, Asteroid and SpaceShip are peer GameObjects. +struct SpaceStation: public GameObject +{ + SpaceStation(const std::string& name): GameObject(name) {} + // Only a dummy SpaceStation is constructed without a name + SpaceStation(): GameObject("dummy") {} +}; + +struct Asteroid: public GameObject +{ + Asteroid(const std::string& name): GameObject(name) {} + Asteroid(): GameObject("dummy") {} +}; + +struct SpaceShip: public GameObject +{ + SpaceShip(const std::string& name): GameObject(name) {} + SpaceShip(): GameObject("dummy") {} +}; + +// SpaceShip is specialized further into CommercialShip and MilitaryShip. +struct CommercialShip: public SpaceShip +{ + CommercialShip(const std::string& name): SpaceShip(name) {} + CommercialShip(): SpaceShip("dummy") {} +}; + +struct MilitaryShip: public SpaceShip +{ + MilitaryShip(const std::string& name): SpaceShip(name) {} + MilitaryShip(): SpaceShip("dummy") {} +}; + +/*-------------------------- Collision functions ---------------------------*/ +// This mechanism permits us to overcome a limitation of Meyers' approach: we +// can declare the parameter types exactly as we want, rather than having to +// make them all GameObject& parameters. +std::string shipAsteroid(SpaceShip& ship, Asteroid& rock) +{ +// std::cout << rock.stringize() << " has pulverized " << ship.stringize() << std::endl; + return "shipAsteroid"; +} + +std::string militaryShipAsteroid(MilitaryShip& ship, Asteroid& rock) +{ +// std::cout << rock.stringize() << " has severely damaged " << ship.stringize() << std::endl; + return "militaryShipAsteroid"; +} + +std::string shipStation(SpaceShip& ship, SpaceStation& dock) +{ +// std::cout << ship.stringize() << " has docked at " << dock.stringize() << std::endl; + return "shipStation"; +} + +std::string asteroidStation(Asteroid& rock, SpaceStation& dock) +{ +// std::cout << rock.stringize() << " has damaged " << dock.stringize() << std::endl; + return "asteroidStation"; +} + +/*------------------------------- Test code --------------------------------*/ +namespace tut +{ + struct dispatch_data + { + dispatch_data(): + home(new SpaceStation("Terra Station")), + obstacle(new Asteroid("Ganymede")), + tug(new CommercialShip("Pilotfish")), + patrol(new MilitaryShip("Enterprise")) + {} + + // Instantiate and populate the DoubleDispatch object. + typedef LLDoubleDispatch DD; + DD dispatcher; + + // Instantiate a few GameObjects. Make sure we refer to them + // polymorphically, and don't let them leak. + std::auto_ptr home; + std::auto_ptr obstacle; + std::auto_ptr tug; + std::auto_ptr patrol; + + // prototype objects + Asteroid dummyAsteroid; + SpaceShip dummyShip; + MilitaryShip dummyMilitary; + CommercialShip dummyCommercial; + SpaceStation dummyStation; + }; + typedef test_group dispatch_group; + typedef dispatch_group::object dispatch_object; + tut::dispatch_group ddgr("double dispatch"); + + template<> template<> + void dispatch_object::test<1>() + { + // Describe param types using explicit DD::Type objects + // (order-sensitive add() variant) + dispatcher.add(DD::Type(), DD::Type(), shipAsteroid, true); + // naive adding, won't work + dispatcher.add(DD::Type(), DD::Type(), militaryShipAsteroid, true); + dispatcher.add(DD::Type(), DD::Type(), shipStation, true); + dispatcher.add(DD::Type(), DD::Type(), asteroidStation, true); + + // Try colliding them. + ensure_equals(dispatcher(*home, *tug), // reverse params, SpaceShip subclass + "shipStation"); + ensure_equals(dispatcher(*patrol, *home), // forward params, SpaceShip subclass + "shipStation"); + ensure_equals(dispatcher(*obstacle, *home), // forward params + "asteroidStation"); + ensure_equals(dispatcher(*home, *obstacle), // reverse params + "asteroidStation"); + ensure_equals(dispatcher(*tug, *obstacle), // forward params, SpaceShip subclass + "shipAsteroid"); + ensure_equals(dispatcher(*obstacle, *patrol), // reverse params, SpaceShip subclass + // won't use militaryShipAsteroid() because it was added + // in wrong order + "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<2>() + { + // Describe param types using explicit DD::Type objects + // (order-sensitive add() variant) + // adding in correct order + dispatcher.add(DD::Type(), DD::Type(), militaryShipAsteroid, true); + dispatcher.add(DD::Type(), DD::Type(), shipAsteroid, true); + dispatcher.add(DD::Type(), DD::Type(), shipStation, true); + dispatcher.add(DD::Type(), DD::Type(), asteroidStation, true); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<3>() + { + // Describe param types with actual prototype instances + // (order-insensitive add() variant) + dispatcher.add(dummyMilitary, dummyAsteroid, militaryShipAsteroid); + dispatcher.add(dummyShip, dummyAsteroid, shipAsteroid); + dispatcher.add(dummyShip, dummyStation, shipStation); + dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + ensure_equals(dispatcher(*obstacle, *patrol), ""); + } + + template<> template<> + void dispatch_object::test<4>() + { + // Describe param types with actual prototype instances + // (order-insensitive add() variant) + dispatcher.add(dummyShip, dummyAsteroid, shipAsteroid); + // Even if we add the militaryShipAsteroid in the wrong order, it + // should still work. + dispatcher.add(dummyMilitary, dummyAsteroid, militaryShipAsteroid); + dispatcher.add(dummyShip, dummyStation, shipStation); + dispatcher.add(dummyAsteroid, dummyStation, asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } + + template<> template<> + void dispatch_object::test<5>() + { + dispatcher.add(shipAsteroid); + dispatcher.add(militaryShipAsteroid); + dispatcher.add(shipStation); + dispatcher.add(asteroidStation); + + ensure_equals(dispatcher(*patrol, *obstacle), "militaryShipAsteroid"); + ensure_equals(dispatcher(*tug, *obstacle), "shipAsteroid"); + } +} // namespace tut diff --git a/indra/test/llpermissions_tut.cpp b/indra/test/llpermissions_tut.cpp index 9dbb9642dd..4eadc64b5a 100644 --- a/indra/test/llpermissions_tut.cpp +++ b/indra/test/llpermissions_tut.cpp @@ -485,15 +485,8 @@ namespace tut template<> template<> void permission_object_t::test<22>() { - LLPermissions perm,perm1; - LLUUID creator("abf0d56b-82e5-47a2-a8ad-74741bb2c29e"); - LLUUID owner("68edcf47-ccd7-45b8-9f90-1649d7f12806"); - LLUUID lastOwner("5e47a0dc-97bf-44e0-8b40-de06718cee9d"); - LLUUID group("9c8eca51-53d5-42a7-bb58-cef070395db8"); - perm.init(creator,owner,lastOwner,group); - LLXMLNode* xml_node = perm.exportFileXML(); - perm1.importXML(xml_node); - ensure("exportFileXML()/importXML():failed to export and import the data ", perm1 == perm); + // Deleted LLPermissions::exportFileXML() and LLPermissions::importXML() + // because I can't find any non-test code references to it. 2009-05-04 JC } template<> template<> diff --git a/indra/test/llsaleinfo_tut.cpp b/indra/test/llsaleinfo_tut.cpp index 7f8219cdc8..fa5e047513 100644 --- a/indra/test/llsaleinfo_tut.cpp +++ b/indra/test/llsaleinfo_tut.cpp @@ -167,19 +167,8 @@ namespace tut template<> template<> void llsaleinfo_test_t::test<4>() { -// LLXMLNode is teh suck. -#if 0 - S32 sale_price = 23445; - LLSaleInfo saleinfo(LLSaleInfo::FS_CONTENTS, sale_price); - - LLXMLNode* x_node = saleinfo.exportFileXML(); - - LLSaleInfo saleinfo1(LLSaleInfo::FS_NOT, 0); - - saleinfo1.importXML(x_node); - ensure_equals("1.importXML() fn failed", saleinfo.getSalePrice(), saleinfo1.getSalePrice()); - ensure_equals("2.importXML() fn failed", saleinfo.getSaleType(), saleinfo1.getSaleType()); -#endif + // Deleted LLSaleInfo::exportFileXML() and LLSaleInfo::importXML() + // because I can't find any non-test code references to it. 2009-05-04 JC } template<> template<> diff --git a/indra/test/llscriptresource_tut.cpp b/indra/test/llscriptresource_tut.cpp index e384c275a3..705fdd16ae 100644 --- a/indra/test/llscriptresource_tut.cpp +++ b/indra/test/llscriptresource_tut.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2006-2007, 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/test/lltimestampcache_tut.cpp b/indra/test/lltimestampcache_tut.cpp index 9e0de0fe60..3b102a3366 100644 --- a/indra/test/lltimestampcache_tut.cpp +++ b/indra/test/lltimestampcache_tut.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 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 @@ -13,12 +13,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/test/lltranscode_tut.cpp b/indra/test/lltranscode_tut.cpp index 8abf9dc224..eb21979db0 100644 --- a/indra/test/lltranscode_tut.cpp +++ b/indra/test/lltranscode_tut.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2008&license=viewergpl$ * - * Copyright (c) 2006-2007, 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/test/test.h b/indra/test/test.h index 16ec4effcf..cee185140c 100644 --- a/indra/test/test.h +++ b/indra/test/test.h @@ -13,12 +13,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, -- 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/test') 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/test') 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/test') 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 a81c084debb4075f36bacd59cbe332c2f0548d50 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 26 May 2009 22:36:38 +0000 Subject: Add llsd_equals(), a function whose absence sorely puzzles me --- indra/llcommon/llsdutil.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++ indra/llcommon/llsdutil.h | 3 ++ indra/test/llsdutil_tut.cpp | 58 +++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 2 deletions(-) (limited to 'indra/test') diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 643720cebe..c8d8030e87 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -576,3 +576,95 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str // bad LLSD doesn't define isConvertible(Type to, Type from). return match_types(prototype.type(), TypeVector(), data.type(), pfx); } + +bool llsd_equals(const LLSD& lhs, const LLSD& rhs) +{ + // We're comparing strict equality of LLSD representation rather than + // performing any conversions. So if the types aren't equal, the LLSD + // values aren't equal. + if (lhs.type() != rhs.type()) + { + return false; + } + + // Here we know both types are equal. Now compare values. + switch (lhs.type()) + { + case LLSD::TypeUndefined: + // Both are TypeUndefined. There's nothing more to know. + return true; + +#define COMPARE_SCALAR(type) \ + case LLSD::Type##type: \ + /* LLSD::URI has operator!=() but not operator==() */ \ + /* rely on the optimizer for all others */ \ + return (! (lhs.as##type() != rhs.as##type())) + + COMPARE_SCALAR(Boolean); + COMPARE_SCALAR(Integer); + // The usual caveats about comparing floating-point numbers apply. This is + // only useful when we expect identical bit representation for a given + // Real value, e.g. for integer-valued Reals. + COMPARE_SCALAR(Real); + COMPARE_SCALAR(String); + COMPARE_SCALAR(UUID); + COMPARE_SCALAR(Date); + COMPARE_SCALAR(URI); + COMPARE_SCALAR(Binary); + +#undef COMPARE_SCALAR + + case LLSD::TypeArray: + { + LLSD::array_const_iterator + lai(lhs.beginArray()), laend(lhs.endArray()), + rai(rhs.beginArray()), raend(rhs.endArray()); + // Compare array elements, walking the two arrays in parallel. + for ( ; lai != laend && rai != raend; ++lai, ++rai) + { + // If any one array element is unequal, the arrays are unequal. + if (! llsd_equals(*lai, *rai)) + return false; + } + // Here we've reached the end of one or the other array. They're equal + // only if they're BOTH at end: that is, if they have equal length too. + return (lai == laend && rai == raend); + } + + case LLSD::TypeMap: + { + // Build a set of all rhs keys. + std::set rhskeys; + for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); + rmi != rmend; ++rmi) + { + rhskeys.insert(rmi->first); + } + // Now walk all the lhs keys. + for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); + lmi != lmend; ++lmi) + { + // Try to erase this lhs key from the set of rhs keys. If rhs has + // no such key, the maps are unequal. erase(key) returns count of + // items erased. + if (rhskeys.erase(lmi->first) != 1) + return false; + // Both maps have the current key. Compare values. + if (! llsd_equals(lmi->second, rhs[lmi->first])) + return false; + } + // We've now established that all the lhs keys have equal values in + // both maps. The maps are equal unless rhs contains a superset of + // those keys. + return rhskeys.empty(); + } + + default: + // We expect that every possible type() value is specifically handled + // above. Failing to extend this switch to support a new LLSD type is + // an error that must be brought to the coder's attention. + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): " + "unknown type " << lhs.type() << LL_ENDL; + return false; // pacify the compiler + } +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index a4175be450..31d219da52 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -129,6 +129,9 @@ LL_COMMON_API BOOL compare_llsd_with_template( */ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); +/// Deep equality +bool llsd_equals(const LLSD& lhs, const LLSD& rhs); + // 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/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp index 35ab80e791..d125bb0005 100644 --- a/indra/test/llsdutil_tut.cpp +++ b/indra/test/llsdutil_tut.cpp @@ -45,6 +45,7 @@ #include "llquaternion.h" #include "llsdutil.h" #include "llsdutil_math.h" +#include "stringize.h" #include #include @@ -207,14 +208,16 @@ namespace tut 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 + // Only an empty array can 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()); + LLSD::map_const_iterator mi, mend(map.endMap()); + + /*-------------------------- llsd_matches --------------------------*/ // empty prototype matches anything for (mi = map.beginMap(); mi != mend; ++mi) @@ -337,5 +340,56 @@ namespace tut static const char* matches[] = { "Binary" }; test_matches("Binary", map, boost::begin(matches), boost::end(matches)); } + + /*-------------------------- llsd_equals ---------------------------*/ + + // Cross-product of each LLSD type with every other + for (LLSD::map_const_iterator lmi(map.beginMap()), lmend(map.endMap()); + lmi != lmend; ++lmi) + { + for (LLSD::map_const_iterator rmi(map.beginMap()), rmend(map.endMap()); + rmi != rmend; ++rmi) + { + // Name this test based on the map keys naming the types of + // interest, e.g "String::Integer". + // We expect the values (xmi->second) to be equal if and only + // if the type names (xmi->first) are equal. + ensure(STRINGIZE(lmi->first << "::" << rmi->first), + bool(lmi->first == rmi->first) == + bool(llsd_equals(lmi->second, rmi->second))); + } + } + + // Array cases + LLSD rarray; + rarray.append(1.0); + rarray.append(2); + rarray.append("3"); + LLSD larray(rarray); + ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray)); + rarray[2] = "4"; + ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray)); + rarray = larray; + rarray.append(LLSD::Date()); + ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray)); + rarray = larray; + rarray.erase(2); + ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray)); + + // Map cases + LLSD rmap; + rmap["San Francisco"] = 65; + rmap["Phoenix"] = 92; + rmap["Boston"] = 77; + LLSD lmap(rmap); + ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap)); + rmap["Boston"] = 80; + ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap)); + rmap = lmap; + rmap["Atlanta"] = 95; + ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap)); + rmap = lmap; + lmap["Seattle"] = 72; + ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap)); } } -- cgit v1.3 From c607752a9dc17aaf2405ef36a78773d1a6400944 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 28 May 2009 21:25:58 +0000 Subject: DEV-32777: ensure that stack objects listening on persistent LLEventPumps get properly disconnected when destroyed. Break out Debug class and associated macros from lleventcoro_test.cpp into test/debug.h. Add Debug output to lllogin_test. --- indra/llcommon/tests/lleventcoro_test.cpp | 50 +--------------- indra/test/CMakeLists.txt | 1 + indra/test/debug.h | 68 ++++++++++++++++++++++ .../viewer_components/login/tests/lllogin_test.cpp | 57 +++++++++++++----- 4 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 indra/test/debug.h (limited to 'indra/test') diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index 695b1ca9f4..3a2cda7735 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -65,55 +65,7 @@ #include "tests/wrapllerrs.h" #include "stringize.h" #include "lleventcoro.h" - -/***************************************************************************** -* Debugging stuff -*****************************************************************************/ -// This class is intended to illuminate entry to a given block, exit from the -// same block and checkpoints along the way. It also provides a convenient -// place to turn std::cout output on and off. -class Debug -{ -public: - Debug(const std::string& block): - mBlock(block) - { - (*this)("entry"); - } - - ~Debug() - { - (*this)("exit"); - } - - void operator()(const std::string& status) - { -// std::cout << mBlock << ' ' << status << std::endl; - } - -private: - const std::string mBlock; -}; - -// It's often convenient to use the name of the enclosing function as the name -// of the Debug block. -#define DEBUG Debug debug(__FUNCTION__) - -// These BEGIN/END macros are specifically for debugging output -- please -// don't assume you must use such for coroutines in general! They only help to -// make control flow (as well as exception exits) explicit. -#define BEGIN \ -{ \ - DEBUG; \ - try - -#define END \ - catch (...) \ - { \ -/* std::cout << "*** exceptional " << std::flush; */ \ - throw; \ - } \ -} +#include "../test/debug.h" /***************************************************************************** * from the banana.cpp example program borrowed for test<1>() diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 88ef15a8d9..02dd01e1b0 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -90,6 +90,7 @@ set(test_SOURCE_FILES set(test_HEADER_FILES CMakeLists.txt + debug.h llpipeutil.h llsdtraits.h lltut.h diff --git a/indra/test/debug.h b/indra/test/debug.h new file mode 100644 index 0000000000..a00659d880 --- /dev/null +++ b/indra/test/debug.h @@ -0,0 +1,68 @@ +/** + * @file debug.h + * @author Nat Goodspeed + * @date 2009-05-28 + * @brief Debug output for unit test code + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_DEBUG_H) +#define LL_DEBUG_H + +#include + +/***************************************************************************** +* Debugging stuff +*****************************************************************************/ +// This class is intended to illuminate entry to a given block, exit from the +// same block and checkpoints along the way. It also provides a convenient +// place to turn std::cout output on and off. +class Debug +{ +public: + Debug(const std::string& block): + mBlock(block) + { + (*this)("entry"); + } + + ~Debug() + { + (*this)("exit"); + } + + void operator()(const std::string& status) + { +#if defined(DEBUG_ON) + std::cout << mBlock << ' ' << status << std::endl; +#endif + } + +private: + const std::string mBlock; +}; + +// It's often convenient to use the name of the enclosing function as the name +// of the Debug block. +#define DEBUG Debug debug(__FUNCTION__) + +// These BEGIN/END macros are specifically for debugging output -- please +// don't assume you must use such for coroutines in general! They only help to +// make control flow (as well as exception exits) explicit. +#define BEGIN \ +{ \ + DEBUG; \ + try + +#define END \ + catch (...) \ + { \ + debug("*** exceptional "); \ + throw; \ + } \ +} + +#endif /* ! defined(LL_DEBUG_H) */ diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 07c9db1099..b9fe59c0a6 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -1,8 +1,8 @@ /** - * @file llviewerlogin_test.cpp + * @file lllogin_test.cpp * @author Mark Palange * @date 2009-02-26 - * @brief Tests of lllazy.h. + * @brief Tests of lllogin.cpp. * * $LicenseInfo:firstyear=2009&license=internal$ * Copyright (c) 2009, Linden Research, Inc. @@ -20,24 +20,29 @@ // other Linden headers #include "llsd.h" #include "../../../test/lltut.h" +//#define DEBUG_ON +#include "../../../test/debug.h" #include "llevents.h" +#include "stringize.h" /***************************************************************************** -* TUT +* Helper classes *****************************************************************************/ // This is a listener to receive results from lllogin. -class LoginListener +class LoginListener: public LLEventTrackable { std::string mName; LLSD mLastEvent; + Debug mDebug; public: LoginListener(const std::string& name) : - mName(name) + mName(name), + mDebug(stringize(*this)) {} bool call(const LLSD& event) { - std::cout << "LoginListener called!: " << event << std::endl; + mDebug(STRINGIZE("LoginListener called!: " << event)); mLastEvent = event; return false; } @@ -47,15 +52,21 @@ public: return pump.listen(mName, boost::bind(&LoginListener::call, this, _1)); } - const LLSD& lastEvent() { return mLastEvent; } + LLSD lastEvent() { return mLastEvent; } + + friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener) + { + return out << "LoginListener(" << listener.mName << ')'; + } }; -class LLAresListener +class LLAresListener: public LLEventTrackable { std::string mName; LLSD mEvent; bool mImmediateResponse; bool mMultipleURIResponse; + Debug mDebug; public: LLAresListener(const std::string& name, @@ -64,12 +75,13 @@ public: ) : mName(name), mImmediateResponse(i), - mMultipleURIResponse(m) + mMultipleURIResponse(m), + mDebug(stringize(*this)) {} bool handle_event(const LLSD& event) { - std::cout << "LLAresListener called!: " << event << std::endl; + mDebug(STRINGIZE("LLAresListener called!: " << event)); mEvent = event; if(mImmediateResponse) { @@ -96,14 +108,20 @@ public: { return pump.listen(mName, boost::bind(&LLAresListener::handle_event, this, _1)); } + + friend std::ostream& operator<<(std::ostream& out, const LLAresListener& listener) + { + return out << "LLAresListener(" << listener.mName << ')'; + } }; -class LLXMLRPCListener +class LLXMLRPCListener: public LLEventTrackable { std::string mName; LLSD mEvent; bool mImmediateResponse; LLSD mResponse; + Debug mDebug; public: LLXMLRPCListener(const std::string& name, @@ -112,7 +130,8 @@ public: ) : mName(name), mImmediateResponse(i), - mResponse(response) + mResponse(response), + mDebug(stringize(*this)) { if(mResponse.isUndefined()) { @@ -131,7 +150,7 @@ public: bool handle_event(const LLSD& event) { - std::cout << "LLXMLRPCListener called!: " << event << std::endl; + mDebug(STRINGIZE("LLXMLRPCListener called!: " << event)); mEvent = event; if(mImmediateResponse) { @@ -149,8 +168,16 @@ public: { return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1)); } + + friend std::ostream& operator<<(std::ostream& out, const LLXMLRPCListener& listener) + { + return out << "LLXMLRPCListener(" << listener.mName << ')'; + } }; +/***************************************************************************** +* TUT +*****************************************************************************/ namespace tut { struct llviewerlogin_data @@ -168,6 +195,7 @@ namespace tut template<> template<> void llviewerlogin_object::test<1>() { + DEBUG; // 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. @@ -201,6 +229,7 @@ namespace tut template<> template<> void llviewerlogin_object::test<2>() { + DEBUG; // 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 @@ -292,6 +321,7 @@ namespace tut template<> template<> void llviewerlogin_object::test<3>() { + DEBUG; // Test completed response, that fails to login. set_test_name("LLLogin valid response, failure (eg. bad credentials)"); @@ -338,6 +368,7 @@ namespace tut template<> template<> void llviewerlogin_object::test<4>() { + DEBUG; // Test incomplete response, that end the attempt. set_test_name("LLLogin valid response, failure (eg. bad credentials)"); -- 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/test') 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 b029846e81f104789d001e56ca0d83fe8c9e93cd Mon Sep 17 00:00:00 2001 From: "Mark Palange (Mani)" Date: Tue, 28 Jul 2009 13:20:23 -0700 Subject: DEV-35399 - Making the server build llcommon.dll compatible. Now using RunBuildTest.cmake to run tut and lscript_lsl tests, inorder to set path to llcommon.dll Exported a few llcommon apis needed by server components/tests. --- indra/cmake/LLCommon.cmake | 2 +- indra/cmake/Linking.cmake | 2 +- indra/llcommon/lldate.h | 4 ++-- indra/llcommon/lldependencies.h | 2 +- indra/llcommon/llliveappconfig.h | 2 +- indra/test/CMakeLists.txt | 23 +++++++++++++++++------ indra/test/llsdmessagebuilder_tut.cpp | 3 ++- 7 files changed, 25 insertions(+), 13 deletions(-) (limited to 'indra/test') diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake index 19d61008da..d1ab264a41 100644 --- a/indra/cmake/LLCommon.cmake +++ b/indra/cmake/LLCommon.cmake @@ -19,5 +19,5 @@ add_definitions(${TCMALLOC_FLAG}) set(LLCOMMON_LINK_SHARED ON CACHE BOOL "Build the llcommon target as a shared library.") if(LLCOMMON_LINK_SHARED) - add_definitions(-DLL_COMMON_LINK_SHARED=1) + add_definitions(-DLL_COMMON_LINK_SHARED=1) endif(LLCOMMON_LINK_SHARED) diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index 4063034837..578edf9989 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 CACHE FILEPATH "Location of staged DLLs") elseif (LINUX) if (VIEWER) set(ARCH_PREBUILT_DIRS ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/lib_release_client) diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index d6982fbd9e..4dffaafc7d 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -155,10 +155,10 @@ private: }; // Helper function to stream out a date -std::ostream& operator<<(std::ostream& s, const LLDate& date); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLDate& date); // Helper function to stream in a date -std::istream& operator>>(std::istream& s, LLDate& date); +LL_COMMON_API std::istream& operator>>(std::istream& s, LLDate& date); const static std::string weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h index bd4bd7c96a..754557cf31 100644 --- a/indra/llcommon/lldependencies.h +++ b/indra/llcommon/lldependencies.h @@ -81,7 +81,7 @@ struct instance_from_range: public TYPE * LLDependencies components that should not be reinstantiated for each KEY, * NODE specialization */ -class LLDependenciesBase +class LL_COMMON_API LLDependenciesBase { public: virtual ~LLDependenciesBase() {} diff --git a/indra/llcommon/llliveappconfig.h b/indra/llcommon/llliveappconfig.h index a6ece6e8b3..73b3a23352 100644 --- a/indra/llcommon/llliveappconfig.h +++ b/indra/llcommon/llliveappconfig.h @@ -45,7 +45,7 @@ * loop. The traditional name for it is live_config. Be sure to call * live_config.checkAndReload() periodically. */ -class LLLiveAppConfig : public LLLiveFile +class LL_COMMON_API LLLiveAppConfig : public LLLiveFile { public: diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 42a32f95b1..7ccd554be4 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -13,7 +13,7 @@ include(LLXML) include(LScript) include(Linking) include(Tut) -include(Boost) + include_directories( ${LLCOMMON_INCLUDE_DIRS} @@ -124,6 +124,7 @@ target_link_libraries(test ${LLXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLCOMMON_LIBRARIES} + ${EXPAT_LIBRARIES} ${APRICONV_LIBRARIES} ${PTHREAD_LIBRARY} ${WINDOWS_LIBRARIES} @@ -143,16 +144,26 @@ endif (WINDOWS) get_target_property(TEST_EXE test LOCATION) -add_custom_command( +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_CMD ${TEST_EXE} --output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt --touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt) + +ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt - COMMAND ${TEST_EXE} + COMMAND ${CMAKE_COMMAND} ARGS - --output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt - --touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt + -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} + -DTEST_CMD:STRING="${TEST_CMD}" + -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake + DEPENDS test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "C++ unit tests" - ) + ) set(test_results ${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt) diff --git a/indra/test/llsdmessagebuilder_tut.cpp b/indra/test/llsdmessagebuilder_tut.cpp index 9edb915703..f4457b6d15 100755 --- a/indra/test/llsdmessagebuilder_tut.cpp +++ b/indra/test/llsdmessagebuilder_tut.cpp @@ -44,7 +44,8 @@ #include "v3dmath.h" #include "v3math.h" #include "v4math.h" -#include "llsdutil.cpp" +#include "llsdutil.h" +//#include "llsdutil.cpp" #include "llsdutil_math.cpp" #include "lltemplatemessagebuilder.h" -- cgit v1.3 From fe339408cefb51023cae8a7fd3def2a4b8a75a65 Mon Sep 17 00:00:00 2001 From: "Mark Palange (Mani)" Date: Tue, 28 Jul 2009 18:03:28 -0700 Subject: DEV-35399 - Making the server build llcommon.dll compatible. Fixing up string escaping on calls to RunBuildTest.cmake Also added ilasm.bat to the FindMono.cmake search --- indra/cmake/FindMono.cmake | 2 +- indra/cmake/LLAddBuildTest.cmake | 14 +++++++------- indra/test/CMakeLists.txt | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'indra/test') diff --git a/indra/cmake/FindMono.cmake b/indra/cmake/FindMono.cmake index c36d7259e8..d956c48656 100644 --- a/indra/cmake/FindMono.cmake +++ b/indra/cmake/FindMono.cmake @@ -42,7 +42,7 @@ FIND_PROGRAM (GACUTIL_EXECUTABLE gacutil /usr/local/bin ) FIND_PROGRAM (ILASM_EXECUTABLE - ilasm + NAMES ilasm.bat ilasm NO_DEFAULT_PATH PATHS "$ENV{PROGRAMFILES}/Mono-1.9.1/bin" "$ENV{PROGRAMFILES}/Mono-1.2.6/bin" /bin /usr/bin /usr/local/bin ) diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 0c6ddef50f..aa77bbac65 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -114,7 +114,8 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) GET_TARGET_PROPERTY(TEST_EXE PROJECT_${project}_TEST_${name} LOCATION) SET(TEST_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/PROJECT_${project}_TEST_${name}_ok.txt) SET(TEST_CMD ${TEST_EXE} --touch=${TEST_OUTPUT} --sourcedir=${CMAKE_CURRENT_SOURCE_DIR}) - # daveh - what configuration does this use? Debug? it's cmake-time, not build time. + poppy 2009-04-19 + + # daveh - what configuration does this use? Debug? it's cmake-time, not build time. + poppy 2009-04-19 IF(LL_TEST_VERBOSE) MESSAGE(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_cmd = ${TEST_CMD}") ENDIF(LL_TEST_VERBOSE) @@ -125,18 +126,17 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:/usr/lib) ENDIF(WINDOWS) - SET(TEST_SCRIPT_CMD - ${CMAKE_COMMAND} - -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} - -DTEST_CMD:STRING="${TEST_CMD}" - -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake - ) IF(LL_TEST_VERBOSE) MESSAGE(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_script = ${TEST_SCRIPT_CMD}") ENDIF(LL_TEST_VERBOSE) # Add test ADD_CUSTOM_COMMAND( OUTPUT ${TEST_OUTPUT} + COMMAND ${CMAKE_COMMAND} + ARGS + -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} + "-DTEST_CMD:STRING=\"${TEST_CMD}\"" + -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake COMMAND ${TEST_SCRIPT_CMD} DEPENDS PROJECT_${project}_TEST_${name} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 7ccd554be4..e596151591 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -157,7 +157,7 @@ ADD_CUSTOM_COMMAND( COMMAND ${CMAKE_COMMAND} ARGS -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} - -DTEST_CMD:STRING="${TEST_CMD}" + "-DTEST_CMD:STRING=\"${TEST_CMD}\"" -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake DEPENDS test -- cgit v1.3 From 2965f5ff382b26d8f98409bf1040a1976ed25908 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Aug 2009 15:51:56 -0400 Subject: Add SHARED_LIB_STAGING_DIR to LD_LIBRARY_PATH for indra/test executable, as with LLAddBuildTest --- indra/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/test') diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 012c5e6408..d8c3d45c5f 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -150,7 +150,7 @@ get_target_property(TEST_EXE test LOCATION) IF(WINDOWS) set(LD_LIBRARY_PATH ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}) ELSE(WINDOWS) - set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:/usr/lib) + set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}:/usr/lib) ENDIF(WINDOWS) SET(TEST_CMD ${TEST_EXE} --output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt --touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt) -- cgit v1.3 From b9546a33e90df60e2d47735b4627de4eb97ba6b9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 8 Sep 2009 12:03:32 -0400 Subject: QAR-1619: Finish replacing RunBuildTest.cmake with run_build_test.py. Because the details of RunBuildTest.cmake versus run_build_test.py had to be changed in so many different places, introduce LL_TEST_COMMAND CMake macro (in LLTestCommand.cmake) to encapsulate construction of the actual command line. Use LL_TEST_COMMAND in LL_ADD_PROJECT_UNIT_TESTS, LL_ADD_INTEGRATION_TEST, the big indra/test monolith and the various LslCompilerMacros. Fix run_build_test.py to pass through the test executable's own options (e.g. --touch, --output) without inspection. Defend it against the case when the platform-specific library path environment variable doesn't yet exist. Make it report errors only on nonzero test-program rc. Remove RunBuildTest.cmake. --- indra/cmake/CMakeLists.txt | 2 +- indra/cmake/LLAddBuildTest.cmake | 16 +++++----------- indra/cmake/LLTestCommand.cmake | 13 +++++++++++++ indra/cmake/run_build_test.py | 22 ++++++++++++++++++---- indra/test/CMakeLists.txt | 11 +++-------- 5 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 indra/cmake/LLTestCommand.cmake (limited to 'indra/test') diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 3ce393b659..4563b59ad2 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -53,6 +53,7 @@ set(cmake_SOURCE_FILES LLPrimitive.cmake LLRender.cmake LLScene.cmake + LLTestCommand.cmake LLUI.cmake LLVFS.cmake LLWindow.cmake @@ -69,7 +70,6 @@ set(cmake_SOURCE_FILES PNG.cmake Python.cmake Prebuilt.cmake - RunBuildTest.cmake TemplateCheck.cmake Tut.cmake UI.cmake diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index e6ef4f1f6b..8cd5796849 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -1,4 +1,5 @@ # -*- cmake -*- +include(LLTestCommand) MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) # Given a project name and a list of sourcefiles (with optional properties on each), @@ -126,17 +127,14 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources) set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}:/usr/lib) ENDIF(WINDOWS) + LL_TEST_COMMAND("${LD_LIBRARY_PATH}" ${TEST_CMD}) + SET(TEST_SCRIPT_CMD ${LL_TEST_COMMAND_value}) IF(LL_TEST_VERBOSE) MESSAGE(STATUS "LL_ADD_PROJECT_UNIT_TESTS ${name} test_script = ${TEST_SCRIPT_CMD}") ENDIF(LL_TEST_VERBOSE) # Add test ADD_CUSTOM_COMMAND( OUTPUT ${TEST_OUTPUT} - COMMAND ${CMAKE_COMMAND} - ARGS - -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} - "-DTEST_CMD:STRING=\"${TEST_CMD}\"" - -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake COMMAND ${TEST_SCRIPT_CMD} DEPENDS PROJECT_${project}_TEST_${name} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -216,12 +214,8 @@ FUNCTION(LL_ADD_INTEGRATION_TEST set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}:/usr/lib) ENDIF(WINDOWS) - SET(TEST_SCRIPT_CMD - ${CMAKE_COMMAND} - -DLD_LIBRARY_PATH="${LD_LIBRARY_PATH}" - -DTEST_CMD:STRING="${test_command}" - -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake - ) + LL_TEST_COMMAND("${LD_LIBRARY_PATH}" ${test_command}) + SET(TEST_SCRIPT_CMD ${LL_TEST_COMMAND_value}) if(TEST_DEBUG) message(STATUS "TEST_SCRIPT_CMD: ${TEST_SCRIPT_CMD}") diff --git a/indra/cmake/LLTestCommand.cmake b/indra/cmake/LLTestCommand.cmake new file mode 100644 index 0000000000..fae5640493 --- /dev/null +++ b/indra/cmake/LLTestCommand.cmake @@ -0,0 +1,13 @@ +MACRO(LL_TEST_COMMAND LD_LIBRARY_PATH) + # nat wonders how Kitware can use the term 'function' for a construct that + # cannot return a value. And yet, variables you set inside a FUNCTION are + # local. Try a MACRO instead. + SET(LL_TEST_COMMAND_value + ${PYTHON_EXECUTABLE} + "${CMAKE_SOURCE_DIR}/cmake/run_build_test.py") + IF(LD_LIBRARY_PATH) + LIST(APPEND LL_TEST_COMMAND_value "-l${LD_LIBRARY_PATH}") + ENDIF(LD_LIBRARY_PATH) + LIST(APPEND LL_TEST_COMMAND_value ${ARGN}) +##MESSAGE(STATUS "Will run: ${LL_TEST_COMMAND_value}") +ENDMACRO(LL_TEST_COMMAND) diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py index 1882b644a0..17bce6f434 100644 --- a/indra/cmake/run_build_test.py +++ b/indra/cmake/run_build_test.py @@ -60,22 +60,35 @@ def main(command, libpath=[], vars={}): raise NotImplemented("run_build_test: unknown platform %s" % sys.platform) lpvars = [] for var in lpvars: - # Split the existing path - dirs = os.environ[var].split(os.pathsep) + # Split the existing path. Bear in mind that the variable in question + # might not exist; instead of KeyError, just use an empty string. + dirs = os.environ.get(var, "").split(os.pathsep) # Append the sequence in libpath +## print "%s += %r" % (var, libpath) dirs.extend(libpath) # Now rebuild the path string. This way we use a minimum of separators # -- and we avoid adding a pointless separator when libpath is empty. os.environ[var] = os.pathsep.join(dirs) # Now handle arbitrary environment variables. The tricky part is ensuring # that all the keys and values we try to pass are actually strings. +## if vars: +## print "Setting:" +## for key, value in vars.iteritems(): +## print "%s=%s" % (key, value) os.environ.update(dict([(str(key), str(value)) for key, value in vars.iteritems()])) # Run the child process. +## print "Running: %s" % " ".join(command) return subprocess.call(command) if __name__ == "__main__": from optparse import OptionParser parser = OptionParser(usage="usage: %prog [options] command args...") + # We want optparse support for the options we ourselves handle -- but we + # DO NOT want it looking at options for the executable we intend to run, + # rejecting them as invalid because we don't define them. So configure the + # parser to stop looking for options as soon as it sees the first + # positional argument (traditional Unix syntax). + parser.disable_interspersed_args() parser.add_option("-D", "--define", dest="vars", default=[], action="append", metavar="VAR=value", help="Add VAR=value to the env variables defined") @@ -92,6 +105,7 @@ if __name__ == "__main__": # want. rc = main(command=args, libpath=opts.libpath, vars=dict([(pair.split('=', 1) + [""])[:2] for pair in opts.vars])) - print >>sys.stderr, "Failure running: %s" % " ".join(args) - print >>sys.stderr, "Error: %s" % rc + if rc not in (None, 0): + print >>sys.stderr, "Failure running: %s" % " ".join(args) + print >>sys.stderr, "Error: %s" % rc sys.exit((rc < 0) and 255 or rc) diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index d8c3d45c5f..3e42f6929b 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -153,16 +153,11 @@ ELSE(WINDOWS) set(LD_LIBRARY_PATH ${ARCH_PREBUILT_DIRS}:${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}:/usr/lib) ENDIF(WINDOWS) -SET(TEST_CMD ${TEST_EXE} --output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt --touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt) - +LL_TEST_COMMAND("${LD_LIBRARY_PATH}" + "${TEST_EXE}" "--output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt" "--touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt") ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt - COMMAND ${CMAKE_COMMAND} - ARGS - -DLD_LIBRARY_PATH=${LD_LIBRARY_PATH} - "-DTEST_CMD:STRING=\"${TEST_CMD}\"" - -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake - + COMMAND ${LL_TEST_COMMAND_value} DEPENDS test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "C++ unit tests" -- cgit v1.3 From 48328db339b5eff70a631f067fb3010b30cb4d3a Mon Sep 17 00:00:00 2001 From: Steve Linden Date: Thu, 10 Sep 2009 15:36:49 -0700 Subject: #ifdef'd out gmock and gtest for LL_WINDOWS. Does not appear to be needed and breaks the build. --- indra/test/test.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/test') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index c9e985c914..e7ba8e33b8 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -54,8 +54,10 @@ # include "ctype_workaround.h" #endif +#ifndef LL_WINDOWS #include #include +#endif namespace tut { @@ -243,7 +245,9 @@ int main(int argc, char **argv) { // The following line must be executed to initialize Google Mock // (and Google Test) before running the tests. +#ifndef LL_WINDOWS ::testing::InitGoogleMock(&argc, argv); +#endif LLError::initForApplication("."); LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(LLError::LEVEL_ERROR); -- cgit v1.3