Skip to content
Snippets Groups Projects
Commit dc934629 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files
parent 3800c0df
No related branches found
No related tags found
No related merge requests found
Showing
with 2033 additions and 53 deletions
......@@ -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)
......
# -*- 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}
)
......
# -*- cmake -*-
set(LLLOGIN_INCLUDE_DIRS
${LIBS_OPEN_DIR}/viewer_components/login
)
set(LLLOGIN_LIBRARIES lllogin)
# -*- 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)
......@@ -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)
/**
* @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 <map>
// std headers
// external library headers
// other Linden headers
#include "llsdserialize.h"
#include "llerror.h"
std::string LLEventDetail::listenerNameForCoro(const void* self)
{
typedef std::map<const void*, std::string> 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;
}
This diff is collapsed.
/**
* @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 <boost/bind.hpp>
// 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();
}
/**
* @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 <boost/function.hpp>
/**
* 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<void()> 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) */
......@@ -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)
......
......@@ -19,7 +19,6 @@
#include <map>
#include <set>
#include <vector>
#include <list>
#include <deque>
#include <stdexcept>
#include <boost/signals2.hpp>
......@@ -28,13 +27,9 @@
#include <boost/enable_shared_from_this.hpp>
#include <boost/utility.hpp> // noncopyable
#include <boost/optional/optional.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/visit_each.hpp>
#include <boost/ref.hpp> // reference_wrapper
#include <boost/type_traits/is_pointer.hpp>
#include <boost/utility/addressof.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/function.hpp>
#include <boost/static_assert.hpp>
#include "llsd.h"
......@@ -111,6 +106,9 @@ typedef LLStandardSignal::slot_type LLEventListener;
/// Result of registering a listener, supports <tt>connected()</tt>,
/// <tt>disconnect()</tt> and <tt>blocked()</tt>
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
* <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method,
* instance, _1))</tt>. 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
* <tt>LLEventPump::listen()</tt>.
* - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt>
* runs, destroying state specific to the subclass. (For instance, a
* <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.)
* - The listening method will not be disconnected until
* <tt>~LLEventTrackable()</tt> 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 <tt>Foo*</tt> pointer that was
* <tt>delete</tt>d 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 <tt>boost::shared_ptr</tt>.
* Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
* involving a <tt>boost::weak_ptr<Foo></tt> 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 @@ class LLEventPump: boost::noncopyable
* themselves. listen() can throw any ListenError; see ListenError
* subclasses.
*
* If (as is typical) you pass a <tt>boost::bind()</tt> 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 <tt>boost::bind()</tt> 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 <tt>boost::weak_ptr</tt>.
* * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the
......@@ -429,6 +487,9 @@ class LLEventPump: boost::noncopyable
/// 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 @@ class LLEventQueue: public LLEventPump
};
/*****************************************************************************
* LLEventTrackable and underpinnings
* Underpinnings
*****************************************************************************/
/**
* LLEventTrackable wraps boost::signals2::trackable, which resembles
* boost::trackable. Derive your listener class from LLEventTrackable instead,
* and use something like
* <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method,
* instance, _1))</tt>. 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
* <tt>LLEventPump::listen()</tt>.
* - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt>
* runs, destroying state specific to the subclass. (For instance, a
* <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.)
* - The listening method will not be disconnected until
* <tt>~LLEventTrackable()</tt> 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 <tt>Foo*</tt> pointer that was
* <tt>delete</tt>d 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 <tt>boost::shared_ptr</tt>.
* Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
* involving a <tt>boost::weak_ptr<Foo></tt> 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
......
......@@ -46,6 +46,11 @@
#endif
#include "llsdserialize.h"
#include "stringize.h"
#include <map>
#include <set>
#include <boost/range.hpp>
// 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<LLSD::Type, std::string> 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("<unknown LLSD type " << type << ">");
}
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<LLSD::Type> 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<LLSD::Type> 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);
}
......@@ -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<typename Input> LLSD llsd_copy_array(Input iter, Input end)
......
/**
* @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 <iostream>
/*****************************************************************************
* 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<std::string> 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) */
/**
* @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_data> 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"
/**
* @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) */
......@@ -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)
......@@ -33,6 +33,7 @@
*/
#include "linden_common.h"
#include "llares.h"
#include <ares_dns.h>
#include <ares_version.h>
......@@ -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)
{
......
......@@ -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 <winsock2.h>
# include <ws2tcpip.h>
# pragma warning(pop)
#endif
#ifdef LL_STANDALONE
......@@ -49,7 +55,10 @@
#include "llrefcount.h"
#include "lluri.h"
#include <boost/shared_ptr.hpp>
class LLQueryResponder;
class LLAresListener;
/**
* @brief Supported DNS RR types.
......@@ -444,6 +453,9 @@ class LLAres
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<LLAresListener> mListener;
};
/**
......
/**
* @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<std::string>& uris)
{
LLSD result;
for (std::vector<std::string>::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));
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment