diff options
61 files changed, 5218 insertions, 503 deletions
| 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 <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; +} 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 <boost/coroutine/coroutine.hpp> +#include <boost/coroutine/future.hpp> +#include <boost/optional.hpp> +#include <string> +#include <stdexcept> +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a <tt>const LLEventPumpOrPumpName&</tt> and you can accept either an + * <tt>LLEventPump&</tt> 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 <tt>const char*</tt> to a function +    /// accepting <tt>const LLEventPumpOrPumpName&</tt> would require two +    /// different implicit conversions: <tt>const char*</tt> -> <tt>const +    /// std::string&</tt> -> <tt>const LLEventPumpOrPumpName&</tt>. +    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<LLEventPump&> 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 <typename LISTENER> +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 <typename LISTENER> +LLVoidListener<LISTENER> voidlistener(const LISTENER& listener) +{ +    return LLVoidListener<LISTENER>(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 <tt>path.isUndefined()</tt>, do nothing. +     * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value +     *   into <tt>dest[path.asString()]</tt>. +     * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a +     *   value into <tt>dest[path.asInteger()]</tt>. +     * * If <tt>path.isArray()</tt>, 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 <tt>requestPump.post()</tt> returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until <tt>requestPump.post()</tt> 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 <tt>replyPump.getName()</tt>. 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 <tt>replyPump.getName()</tt> + *   in <tt>event[replyPumpNamePath.asInteger()]</tt>. + * * @c isString(): @a event is a map. Store <tt>replyPump.getName()</tt> in + *   <tt>event[replyPumpNamePath.asString()]</tt>. + * * @c isArray(): @a event has several levels of structure, e.g. map of + *   maps, array of arrays, array of maps, map of arrays, ... Store + *   <tt>replyPump.getName()</tt> in + *   <tt>event[replyPumpNamePath[0]][replyPumpNamePath[1]]...</tt> In other + *   words, examine each array entry in @a replyPumpNamePath in turn. If it's an + *   <tt>LLSD::String</tt>, the current level of @a event is a map; step down to + *   that map entry. If it's an <tt>LLSD::Integer</tt>, 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 <tt>replyPump.getName()</tt>. + */ +template <typename SELF> +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, +                 const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ +    // declare the future +    boost::coroutines::future<LLSD> 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 <typename SELF> +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<LLSD, int> 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 <typename LISTENER> +    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 <typename LISTENER> +    WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator) +    { +        return WaitForEventOnHelper<LISTENER>(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 <typename SELF> +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<LLEventWithID> 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 <typename SELF> +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 <typename SELF> +    LLSD wait(SELF& self) +    { +        return waitForEventOn(self, mPump); +    } + +    template <typename SELF> +    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<std::string, std::string> getNames() const +    { +        return std::pair<std::string, std::string>(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 <typename SELF> +    LLEventWithID wait(SELF& self) +    { +        return waitForEventOn(self, mPump0, mPump1); +    } + +    /// errorException(wait(self)) +    template <typename SELF> +    LLSD waitWithException(SELF& self) +    { +        return errorException(wait(self), std::string("Error event on ") + getName1()); +    } + +    /// errorLog(wait(self)) +    template <typename SELF> +    LLSD waitWithLog(SELF& self) +    { +        return errorLog(wait(self), std::string("Error event on ") + getName1()); +    } + +    template <typename SELF> +    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 <typename SELF> +    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 <typename SELF> +    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 <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(); +} 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 <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) */ 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 <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 -- @@ -255,13 +253,61 @@ namespace LLEventDetail  } // 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 @@ public:       * 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 @@ 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,48 +564,9 @@ 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 - * <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   * LLEventPump::listen(...) and then pass the returned LLBoundListener to 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 <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); +} 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<typename Input> 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 <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) */ 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_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" 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 <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)  	{ 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 <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 @@ 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<LLAresListener> 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<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)); +} 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 <string> +#include <map> +#include <boost/function.hpp> +#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<void(const LLSD&)> Callable; +    typedef std::map<std::string, Callable> 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 <iostream> +// std headers +// external library headers +#include <boost/bind.hpp> + +// 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<std::string> 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<std::string>& uris) {} + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct data +    { +        LLAres dummyAres; +    }; +    typedef test_group<data> llareslistener_group; +    typedef llareslistener_group::object object; +    llareslistener_group llareslistenergrp("llareslistener"); + +	struct ResponseCallback +	{ +		std::vector<std::string> 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<U32, std::string> 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 <boost/function.hpp>  class LLButton;  class LLRadioGroup; @@ -57,8 +58,12 @@ public:  		TOS_CRITICAL_MESSAGE = 1  	}; +	typedef boost::function<void(bool)> 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<LLPointer<LLViewerInventoryCategory>, 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<LLViewerInventoryCategory> 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<LLViewerInventoryCategory> 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<LLViewerInventoryItem> 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<LLViewerInventoryItem> 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<std::string, std::string> response_t; -	typedef std::vector<response_t> 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<std::string> 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<std::string> 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 <boost/scoped_ptr.hpp> +class LLLogin; + +// This class hosts the login module and is used to  +// negotiate user authentication attempts. +class LLLoginInstance : public LLSingleton<LLLoginInstance> +{ +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<LLLogin> 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<LLImageGL> 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 <boost/scoped_ptr.hpp> +  class LLHost; +class LLLogin;  enum EGridInfo  { @@ -74,6 +77,7 @@ class LLViewerLogin : public LLSingleton<LLViewerLogin>  {  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 <map> +#include <set> +// std headers +// external library headers +#include <boost/scoped_ptr.hpp> +#include <boost/range.hpp>          // boost::begin(), boost::end() +// other Linden headers +#include "llerror.h" +#include "stringize.h" +#include "llxmlrpctransaction.h" + +#include <xmlrpc-epi/xmlrpc.h> + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +template <typename STATUS> +class StatusMapperBase +{ +    typedef std::map<STATUS, std::string> 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("<unknown " << mDesc << " " << status << ">"); +    } + +protected: +    std::string mDesc; +    MapType mMap; +}; + +class StatusMapper: public StatusMapperBase<LLXMLRPCTransaction::Status> +{ +public: +    StatusMapper(): StatusMapperBase<LLXMLRPCTransaction::Status>("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<CURLcode> +{ +public: +    CURLcodeMapper(): StatusMapperBase<CURLcode>("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<std::string> 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<std::string>::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("<bad XMLRPC type " << type << '>')); +                status_string = "BadType"; +            } +        } +        return responses; +    } + +    const std::string mUri; +    const std::string mMethod; +    const std::string mReplyPump; +    LLTempBoundListener mBoundListener; +    boost::scoped_ptr<LLXMLRPCTransaction> 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 <iomanip> +// 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<data> 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<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; -} -  template<typename T>  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<LLEventPump::NameList>(list_of(listener0.getName()))); +        LLBoundListener bound0 = listener0.listenTo(per_frame, &Listener::callstop); +        LLBoundListener bound1 = listener1.listenTo(per_frame, &Listener::call, +                                                    // after listener0 +                                                    make<LLEventPump::NameList>(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<LLEventQueue*>(&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 <set> +#include <boost/range.hpp>  namespace tut  {  	struct llsdutil_data  	{ +        void test_matches(const std::string& proto_key, const LLSD& possibles, +                          const char** begin, const char** end) +        { +            std::set<std::string> 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<std::string>::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_data> 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 <boost/coroutine/coroutine.hpp> +#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 <boost/bind.hpp> +#include <boost/scoped_ptr.hpp> + +#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<void(const std::string&, const LLSD&)> coroutine_type; + +    void login_(coroutine_type::self& self, const std::string& uri, const LLSD& credentials); + +    boost::scoped_ptr<coroutine_type> 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<std::string> uris; +// LLViewerLogin::getInstance()->getLoginURIs(uris); +// std::vector<std::string>::const_iterator iter, end; +// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter) +// { +//	std::vector<std::string> 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 <boost/scoped_ptr.hpp> + +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<Impl> 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 <iostream> +// 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_data> 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 @@            <key>darwin</key>            <map>              <key>md5sum</key> -            <string>081ef195a856c708cc473c4421b4b290</string> +            <string>95dda5da1fb66b690a03944fca1b2c53</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090223.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090427.tar.bz2</uri>            </map>            <key>linux</key>            <map>              <key>md5sum</key> -            <string>b516a8576ecad0f957db7fc2cd972aac</string> +            <string>33e2d48a6c2207ade0f914fff99c18af</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090223a.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090427.tar.bz2</uri>            </map>            <key>linux64</key>            <map>              <key>md5sum</key> -            <string>6db62bb7f141b3a1b3107e1f9aad0eb0</string> +            <string>cadb1934581b20f9b03aa18e2be7c55c</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090223a.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090427.tar.bz2</uri>            </map>            <key>windows</key>            <map>              <key>md5sum</key> -            <string>3b56fe9e8d2975c612639d0a5370ffe7</string> +            <string>c3ce8993eac0ca9546564d04131dc4f4</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090225.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090430.tar.bz2</uri>            </map>          </map>        </map> @@ -1132,6 +1132,53 @@ anguage Infrstructure (CLI) international standard</string>            </map>          </map>        </map> +      <key>pth</key> +      <map> +        <key>copyright</key> +        <string>Copyright (c) 1999-2006 Ralf S. Engelschall <rse@gnu.org></string> +        <key>description</key> +        <string>Portable cooperative threads package, used to support Boost.Coroutine on Mac OS X 10.4</string> +        <key>license</key> +        <string>lgpl</string> +        <key>packages</key> +        <map> +          <key>darwin</key> +          <map> +            <key>md5sum</key> +            <string>533f4c710a209a6c4f205f81ccc0cfce</string> +            <key>url</key> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-darwin-20090427.tar.bz2</uri> +          </map> +          <key>linux</key> +          <map> +            <key>md5sum</key> +            <string>c5c2f73847c126e679d925beab48c7d4</string> +            <key>url</key> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux-20090427.tar.bz2</uri> +          </map> +          <key>linux32</key> +          <map> +            <key>md5sum</key> +            <string>c5c2f73847c126e679d925beab48c7d4</string> +            <key>url</key> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux32-20090427.tar.bz2</uri> +          </map> +          <key>linux64</key> +          <map> +            <key>md5sum</key> +            <string>c5c2f73847c126e679d925beab48c7d4</string> +            <key>url</key> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-linux64-20090427.tar.bz2</uri> +          </map> +          <key>windows</key> +          <map> +            <key>md5sum</key> +            <string>c5c2f73847c126e679d925beab48c7d4</string> +            <key>url</key> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/pth-2.0.7-windows-20090427.tar.bz2</uri> +          </map> +        </map> +      </map>        <key>quicktime</key>        <map>          <key>copyright</key> | 
