summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore1
-rw-r--r--indra/llmessage/CMakeLists.txt2
-rw-r--r--indra/llmessage/llexperiencecache.cpp612
-rw-r--r--indra/llmessage/llexperiencecache.h84
-rwxr-xr-xindra/newview/CMakeLists.txt4
-rw-r--r--indra/newview/llappviewer.cpp50
-rw-r--r--indra/newview/llappviewer.h5
-rw-r--r--indra/newview/llfloaterexperiences.cpp14
-rw-r--r--indra/newview/llfloaterexperiences.h45
-rw-r--r--indra/newview/llpanelexperiences.cpp302
-rw-r--r--indra/newview/llpanelexperiences.h119
-rwxr-xr-xindra/newview/llstartup.cpp12
-rwxr-xr-xindra/newview/llstartup.h1
-rw-r--r--indra/newview/llviewerfloaterreg.cpp4
-rw-r--r--indra/newview/llviewerregion.cpp2
-rwxr-xr-xindra/newview/llvoavatar.cpp3
-rw-r--r--indra/newview/skins/default/xui/en/floater_experiences.xml29
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml8
-rw-r--r--indra/newview/skins/default/xui/en/panel_experience_info.xml79
-rw-r--r--indra/newview/skins/default/xui/en/panel_experiences.xml40
20 files changed, 1411 insertions, 5 deletions
diff --git a/.hgignore b/.hgignore
index b180d92003..35651757e7 100644
--- a/.hgignore
+++ b/.hgignore
@@ -70,3 +70,4 @@ glob:indra/newview/avatar_icons_cache.txt
glob:indra/newview/avatar_lad.log
glob:*.diff
*.rej
+indra/newview/stat
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index d98781e9e6..9974d103a2 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -38,6 +38,7 @@ set(llmessage_SOURCE_FILES
llcurl.cpp
lldatapacker.cpp
lldispatcher.cpp
+ llexperiencecache.cpp
llfiltersd2xmlrpc.cpp
llhost.cpp
llhttpassetstorage.cpp
@@ -128,6 +129,7 @@ set(llmessage_HEADER_FILES
lldbstrings.h
lldispatcher.h
lleventflags.h
+ llexperiencecache.h
llfiltersd2xmlrpc.h
llfollowcamparams.h
llhost.h
diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp
new file mode 100644
index 0000000000..18b950b61e
--- /dev/null
+++ b/indra/llmessage/llexperiencecache.cpp
@@ -0,0 +1,612 @@
+/**
+ * @file llexperiencecache.cpp
+ * @brief llexperiencecache and related class definitions
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+#include "llexperiencecache.h"
+
+#include "llavatarname.h"
+#include "llframetimer.h"
+#include "llhttpclient.h"
+#include "llsdserialize.h"
+#include <set>
+#include <map>
+#include "boost/tokenizer.hpp"
+
+
+
+
+
+namespace LLExperienceCache
+{
+ const std::string& MAP_KEY = PUBLIC_KEY;
+ std::string sLookupURL;
+
+ typedef std::map<LLUUID, std::string> ask_queue_t;
+ ask_queue_t sAskQueue;
+
+ typedef std::map<LLUUID, F64> pending_queue_t;
+ pending_queue_t sPendingQueue;
+
+ cache_t sCache;
+ int sMaximumLookups = 10;
+
+ LLFrameTimer sRequestTimer;
+
+ // Periodically clean out expired entries from the cache
+ LLFrameTimer sEraseExpiredTimer;
+
+ // May have multiple callbacks for a single ID, which are
+ // represented as multiple slots bound to the signal.
+ // Avoid copying signals via pointers.
+ typedef std::map<LLUUID, callback_signal_t*> signal_map_t;
+ signal_map_t sSignalMap;
+
+
+ bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age);
+ void eraseExpired();
+
+ void processExperience( const LLUUID& public_key, const LLSD& experience )
+ {
+ sCache[public_key]=experience;
+ LLSD & row = sCache[public_key];
+
+ if(row.has("expires"))
+ {
+ row["expires"] = row["expires"].asReal() + LLFrameTimer::getTotalSeconds();
+ }
+
+ if(row.has(PUBLIC_KEY))
+ {
+ sPendingQueue.erase(row[PUBLIC_KEY].asUUID());
+ }
+
+ if(row.has(PRIVATE_KEY))
+ {
+ sPendingQueue.erase(row[PRIVATE_KEY].asUUID());
+ }
+
+ if(row.has(CREATOR_KEY))
+ {
+ sPendingQueue.erase(row[CREATOR_KEY].asUUID());
+ }
+
+
+ //signal
+ signal_map_t::iterator sig_it = sSignalMap.find(public_key);
+ if (sig_it != sSignalMap.end())
+ {
+ callback_signal_t* signal = sig_it->second;
+ (*signal)(experience);
+
+ sSignalMap.erase(public_key);
+
+ delete signal;
+ }
+ }
+
+ void initClass( )
+ {
+ }
+
+ const cache_t& getCached()
+ {
+ return sCache;
+ }
+
+ void setMaximumLookups( int maximumLookups)
+ {
+ sMaximumLookups = maximumLookups;
+ }
+
+
+ bool expirationFromCacheControl(LLSD headers, F64 *expires)
+ {
+ // Allow the header to override the default
+ LLSD cache_control_header = headers["cache-control"];
+ if (cache_control_header.isDefined())
+ {
+ S32 max_age = 0;
+ std::string cache_control = cache_control_header.asString();
+ if (max_age_from_cache_control(cache_control, &max_age))
+ {
+ LL_WARNS("ExperienceCache")
+ << "got expiration from headers, max_age " << max_age
+ << LL_ENDL;
+ F64 now = LLFrameTimer::getTotalSeconds();
+ *expires = now + (F64)max_age;
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ static const std::string MAX_AGE("max-age");
+ static const boost::char_separator<char> EQUALS_SEPARATOR("=");
+ static const boost::char_separator<char> COMMA_SEPARATOR(",");
+
+ bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
+ {
+ // Split the string on "," to get a list of directives
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ tokenizer directives(cache_control, COMMA_SEPARATOR);
+
+ tokenizer::iterator token_it = directives.begin();
+ for ( ; token_it != directives.end(); ++token_it)
+ {
+ // Tokens may have leading or trailing whitespace
+ std::string token = *token_it;
+ LLStringUtil::trim(token);
+
+ if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
+ {
+ // ...this token starts with max-age, so let's chop it up by "="
+ tokenizer subtokens(token, EQUALS_SEPARATOR);
+ tokenizer::iterator subtoken_it = subtokens.begin();
+
+ // Must have a token
+ if (subtoken_it == subtokens.end()) return false;
+ std::string subtoken = *subtoken_it;
+
+ // Must exactly equal "max-age"
+ LLStringUtil::trim(subtoken);
+ if (subtoken != MAX_AGE) return false;
+
+ // Must have another token
+ ++subtoken_it;
+ if (subtoken_it == subtokens.end()) return false;
+ subtoken = *subtoken_it;
+
+ // Must be a valid integer
+ // *NOTE: atoi() returns 0 for invalid values, so we have to
+ // check the string first.
+ // *TODO: Do servers ever send "0000" for zero? We don't handle it
+ LLStringUtil::trim(subtoken);
+ if (subtoken == "0")
+ {
+ *max_age = 0;
+ return true;
+ }
+ S32 val = atoi( subtoken.c_str() );
+ if (val > 0 && val < S32_MAX)
+ {
+ *max_age = val;
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+
+ void importFile(std::istream& istr)
+ {
+ LLSD data;
+ S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
+ if(parse_count < 1) return;
+
+ LLSD experiences = data["experiences"];
+
+ LLUUID public_key;
+ LLSD::map_const_iterator it = experiences.beginMap();
+ for(; it != experiences.endMap() ; ++it)
+ {
+ public_key.set(it->first);
+ sCache[public_key]=it->second;
+ }
+
+ LL_INFOS("ExperienceCache") << "loaded " << sCache.size() << LL_ENDL;
+ }
+
+ void exportFile(std::ostream& ostr)
+ {
+ LLSD experiences;
+
+ cache_t::const_iterator it =sCache.begin();
+ for( ; it != sCache.end() ; ++it)
+ {
+ if(!it->second.has(PUBLIC_KEY) || it->second[PUBLIC_KEY].asUUID().isNull() ||
+ it->second.has("error"))
+ continue;
+
+ experiences[it->first.asString()] = it->second;
+ }
+
+ LLSD data;
+ data["experiences"] = experiences;
+
+ LLSDSerialize::toPrettyXML(data, ostr);
+ }
+
+ class LLExperienceResponder : public LLHTTPClient::Responder
+ {
+ public:
+ LLExperienceResponder(const ask_queue_t& keys)
+ :mKeys(keys)
+ {
+
+ }
+
+ virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content)
+ {
+ mHeaders = content;
+ }
+
+ virtual void result(const LLSD& content)
+ {
+ LLSD experiences = content["experience_keys"];
+ LLSD::array_const_iterator it = experiences.beginArray();
+ for( /**/ ; it != experiences.endArray(); ++it)
+ {
+ const LLSD& row = *it;
+ LLUUID public_key = row[PUBLIC_KEY].asUUID();
+
+
+ LL_INFOS("ExperienceCache") << "Received result for " << public_key
+ << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL ;
+
+ processExperience(public_key, row);
+ }
+
+ LLSD error_ids = content["error_ids"];
+ LLSD::map_const_iterator errIt = error_ids.beginMap();
+ for( /**/ ; errIt != error_ids.endMap() ; ++errIt )
+ {
+ LLUUID id = LLUUID(errIt->first);
+ for( it = errIt->second.beginArray(); it != errIt->second.endArray() ; ++it)
+ {
+ LL_INFOS("ExperienceCache") << "Clearing error result for " << id
+ << " of type '" << it->asString() << "'" << LL_ENDL ;
+
+ erase(id, it->asString());
+ }
+ }
+
+ LL_INFOS("ExperienceCache") << sCache.size() << " cached experiences" << LL_ENDL;
+ }
+
+ virtual void error(U32 status, const std::string& reason)
+ {
+ LL_WARNS("ExperienceCache") << "Request failed "<<status<<" "<<reason<< LL_ENDL;
+ // We're going to construct a dummy record and cache it for a while,
+ // either briefly for a 503 Service Unavailable, or longer for other
+ // errors.
+ F64 retry_timestamp = errorRetryTimestamp(status);
+
+
+ // Add dummy records for all agent IDs in this request
+ ask_queue_t::const_iterator it = mKeys.begin();
+ for ( ; it != mKeys.end(); ++it)
+ {
+ LLSD exp;
+ exp["expires"]=retry_timestamp;
+ exp[it->second] = it->first;
+ exp["key_type"] = it->second;
+ exp["uuid"] = it->first;
+ exp["error"] = (LLSD::Integer)status;
+ LLExperienceCache::processExperience(it->first, exp);
+ }
+
+ }
+
+ // Return time to retry a request that generated an error, based on
+ // error type and headers. Return value is seconds-since-epoch.
+ F64 errorRetryTimestamp(S32 status)
+ {
+
+ // Retry-After takes priority
+ LLSD retry_after = mHeaders["retry-after"];
+ if (retry_after.isDefined())
+ {
+ // We only support the delta-seconds type
+ S32 delta_seconds = retry_after.asInteger();
+ if (delta_seconds > 0)
+ {
+ // ...valid delta-seconds
+ return F64(delta_seconds);
+ }
+ }
+
+ // If no Retry-After, look for Cache-Control max-age
+ F64 expires = 0.0;
+ if (LLExperienceCache::expirationFromCacheControl(mHeaders, &expires))
+ {
+ return expires;
+ }
+
+ // No information in header, make a guess
+ if (status == 503)
+ {
+ // ...service unavailable, retry soon
+ const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
+ return SERVICE_UNAVAILABLE_DELAY;
+ }
+ else if (status == 499)
+ {
+ // ...we were probably too busy, retry quickly
+ const F64 BUSY_DELAY = 10.0; // 10 seconds
+ return BUSY_DELAY;
+
+ }
+ else
+ {
+ // ...other unexpected error
+ const F64 DEFAULT_DELAY = 3600.0; // 1 hour
+ return DEFAULT_DELAY;
+ }
+ }
+
+ private:
+ ask_queue_t mKeys;
+ LLSD mHeaders;
+ };
+
+ void requestExperiences()
+ {
+ if(sAskQueue.empty() || sLookupURL.empty())
+ return;
+
+ F64 now = LLFrameTimer::getTotalSeconds();
+
+ const U32 EXP_URL_SEND_THRESHOLD = 3000;
+
+
+ std::ostringstream ostr;
+
+ ask_queue_t keys;
+
+ ostr << sLookupURL;
+
+ char arg='?';
+
+ int request_count = 0;
+ for(ask_queue_t::const_iterator it = sAskQueue.begin() ; it != sAskQueue.end() && request_count < sMaximumLookups; ++it)
+ {
+ const LLUUID& key = it->first;
+ const std::string& key_type = it->second;
+
+ ostr << arg << key_type << '=' << key.asString() ;
+
+ keys[key]=key_type;
+ request_count++;
+
+ sPendingQueue[key] = now;
+
+ arg='&';
+
+ if(ostr.tellp() > EXP_URL_SEND_THRESHOLD)
+ {
+ LL_INFOS("ExperienceCache") << " query: " << ostr.str() << LL_ENDL;
+ LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys));
+ ostr.clear();
+ ostr.str(sLookupURL);
+ arg='?';
+ keys.clear();
+ }
+ }
+
+ if(ostr.tellp() > sLookupURL.size())
+ {
+ LL_INFOS("ExperienceCache") << " query: " << ostr.str() << LL_ENDL;
+ LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys));
+ }
+
+ sAskQueue.clear();
+ }
+
+ bool isRequestPending(const LLUUID& public_key)
+ {
+ bool isPending = false;
+ const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
+
+ pending_queue_t::const_iterator it = sPendingQueue.find(public_key);
+
+ if(it != sPendingQueue.end())
+ {
+ F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
+ isPending = (it->second > expire_time);
+ }
+
+ return isPending;
+ }
+
+
+ void setLookupURL( const std::string& lookup_url )
+ {
+ sLookupURL = lookup_url;
+ if(!sLookupURL.empty())
+ {
+ sLookupURL += "id/";
+ }
+ }
+
+ bool hasLookupURL()
+ {
+ return !sLookupURL.empty();
+ }
+
+ void idle()
+ {
+
+ const F32 SECS_BETWEEN_REQUESTS = 0.1f;
+ if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS))
+ {
+ return;
+ }
+
+ // Must be large relative to above
+ const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
+ if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
+ {
+ eraseExpired();
+ }
+
+
+ if(!sAskQueue.empty())
+ {
+ requestExperiences();
+ }
+ }
+
+ struct FindByKey
+ {
+ FindByKey(const LLUUID& key, const std::string& key_type):mKey(key), mKeyType(key_type){}
+ const LLUUID& mKey;
+ const std::string& mKeyType;
+
+ bool operator()(cache_t::value_type& experience)
+ {
+ return experience.second.has(mKeyType) && experience.second[mKeyType].asUUID() == mKey;
+ }
+ };
+
+
+ cache_t::iterator Find(const LLUUID& key, const std::string& key_type)
+ {
+ LL_INFOS("ExperienceCache") << " searching for " << key << " of type " << key_type << LL_ENDL;
+ if(key_type == MAP_KEY)
+ {
+ return sCache.find(key);
+ }
+
+ return std::find_if(sCache.begin(), sCache.end(), FindByKey(key, key_type));
+ }
+
+
+ void erase( const LLUUID& key, const std::string& key_type )
+ {
+ cache_t::iterator it = Find(key, key_type);
+
+ if(it != sCache.end())
+ {
+ sCache.erase(it);
+ }
+ }
+
+ void eraseExpired()
+ {
+ F64 now = LLFrameTimer::getTotalSeconds();
+ cache_t::iterator it = sCache.begin();
+ while (it != sCache.end())
+ {
+ cache_t::iterator cur = it;
+ LLSD& exp = cur->second;
+ ++it;
+ if(exp.has("expires") && exp["expires"].asReal() < now)
+ {
+ if(exp.has("key_type") && exp.has("uuid"))
+ {
+ fetch(exp["uuid"].asUUID(), exp["key_type"].asString(), true);
+ sCache.erase(cur);
+ }
+ else if(exp.has(MAP_KEY))
+ {
+ LLUUID id = exp[MAP_KEY];
+ if(!id.isNull())
+ {
+ fetch(id, MAP_KEY, true);
+ }
+ }
+ }
+ }
+ }
+
+
+ bool fetch( const LLUUID& key, const std::string& key_type, bool refresh/* = true*/ )
+ {
+ if(!key.isNull() && !isRequestPending(key) && (refresh || Find(key, key_type)==sCache.end()))
+ {
+ LL_INFOS("ExperienceCache") << " queue request for " << key_type << " " << key << LL_ENDL ;
+ sAskQueue[key]=key_type;
+
+ return true;
+ }
+ return false;
+ }
+
+ void insert(const LLSD& experience_data )
+ {
+ if(experience_data.has(MAP_KEY))
+ {
+ sCache[experience_data[MAP_KEY].asUUID()]=experience_data;
+ }
+ else
+ {
+ LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << MAP_KEY << LL_ENDL;
+ }
+ }
+
+ bool get( const LLUUID& key, const std::string& key_type, LLSD& experience_data )
+ {
+ if(key.isNull()) return false;
+ cache_t::const_iterator it = Find(key, key_type);
+
+ if (it != sCache.end())
+ {
+ experience_data = it->second;
+ return true;
+ }
+
+ fetch(key, key_type);
+
+ return false;
+ }
+
+
+ void get( const LLUUID& key, const std::string& key_type, callback_slot_t slot )
+ {
+ if(key.isNull()) return;
+
+ cache_t::const_iterator it = Find(key, key_type);
+ if (it != sCache.end())
+ {
+ // ...name already exists in cache, fire callback now
+ callback_signal_t signal;
+ signal.connect(slot);
+
+ signal(it->second);
+ return;
+ }
+
+ fetch(key, key_type);
+
+ // always store additional callback, even if request is pending
+ signal_map_t::iterator sig_it = sSignalMap.find(key);
+ if (sig_it == sSignalMap.end())
+ {
+ // ...new callback for this id
+ callback_signal_t* signal = new callback_signal_t();
+ signal->connect(slot);
+ sSignalMap[key] = signal;
+ }
+ else
+ {
+ // ...existing callback, bind additional slot
+ callback_signal_t* signal = sig_it->second;
+ signal->connect(slot);
+ }
+ }
+
+}
diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h
new file mode 100644
index 0000000000..396a244935
--- /dev/null
+++ b/indra/llmessage/llexperiencecache.h
@@ -0,0 +1,84 @@
+/**
+ * @file llexperiencecache.h
+ * @brief Caches information relating to experience keys
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+
+
+#ifndef LL_LLEXPERIENCECACHE_H
+#define LL_LLEXPERIENCECACHE_H
+
+#include "linden_common.h"
+#include <boost/signals2.hpp>
+
+class LLSD;
+class LLUUID;
+
+
+
+namespace LLExperienceCache
+{
+ const std::string PUBLIC_KEY = "public-id";
+ const std::string PRIVATE_KEY = "private-id";
+ const std::string CREATOR_KEY = "creator-id";
+ const std::string NAME = "name";
+ const std::string PROPERTIES = "properties";
+ const std::string EXPIRES = "expires";
+
+ const int EXPERIENCE_INVALID = (1 << 0);
+ const int EXPERIENCE_NORMAL = (1 << 1);
+ const int EXPERIENCE_REGION = (1 << 2);
+ const static F64 DEFAULT_EXPIRATION = 600.0;
+
+ // dummy name used when we have nothing else
+ const std::string DUMMY_NAME = "\?\?\?";
+ // Callback types for get() below
+ typedef boost::signals2::signal<void (const LLSD& experience)>
+ callback_signal_t;
+ typedef callback_signal_t::slot_type callback_slot_t;
+ typedef std::map<LLUUID, LLSD> cache_t;
+
+
+ void setLookupURL(const std::string& lookup_url);
+ bool hasLookupURL();
+
+ void setMaximumLookups(int maximumLookups);
+
+ void idle();
+ void exportFile(std::ostream& ostr);
+ void importFile(std::istream& istr);
+ void initClass();
+
+ void erase(const LLUUID& key, const std::string& key_type);
+ bool fetch(const LLUUID& key, const std::string& key_type, bool refresh = false);
+ void insert(LLSD& experience_data);
+ bool get(const LLUUID& key, const std::string& key_type, LLSD& experience_data);
+
+ // If name information is in cache, callback will be called immediately.
+ void get(const LLUUID& key, const std::string& key_type, callback_slot_t slot);
+
+ const cache_t& getCached();
+};
+
+#endif // LL_LLEXPERIENCECACHE_H
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index dff2c04fbc..20999c9028 100755
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -202,6 +202,7 @@ set(viewer_SOURCE_FILES
llfloatereditwater.cpp
llfloaterenvironmentsettings.cpp
llfloaterevent.cpp
+ llfloaterexperiences.cpp
llfloaterfonttest.cpp
llfloatergesture.cpp
llfloatergodtools.cpp
@@ -371,6 +372,7 @@ set(viewer_SOURCE_FILES
llpanelclassified.cpp
llpanelcontents.cpp
llpaneleditwearable.cpp
+ llpanelexperiences.cpp
llpanelface.cpp
llpanelgenerictip.cpp
llpanelgroup.cpp
@@ -779,6 +781,7 @@ set(viewer_HEADER_FILES
llfloatereditwater.h
llfloaterenvironmentsettings.h
llfloaterevent.h
+ llfloaterexperiences.h
llfloaterfonttest.h
llfloatergesture.h
llfloatergodtools.h
@@ -942,6 +945,7 @@ set(viewer_HEADER_FILES
llpanelclassified.h
llpanelcontents.h
llpaneleditwearable.h
+ llpanelexperiences.h
llpanelface.h
llpanelgenerictip.h
llpanelgroup.h
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 30f82926f1..c4fdc1ee21 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -100,6 +100,7 @@
// Linden library includes
#include "llavatarnamecache.h"
#include "lldiriterator.h"
+#include "llexperiencecache.h"
#include "llimagej2c.h"
#include "llmemory.h"
#include "llprimitive.h"
@@ -4149,7 +4150,7 @@ void LLAppViewer::loadNameCache()
}
void LLAppViewer::saveNameCache()
- {
+{
// display names cache
std::string filename =
gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml");
@@ -4157,7 +4158,7 @@ void LLAppViewer::saveNameCache()
if(name_cache_stream.is_open())
{
LLAvatarNameCache::exportFile(name_cache_stream);
-}
+ }
if (!gCacheName) return;
@@ -4170,6 +4171,32 @@ void LLAppViewer::saveNameCache()
}
}
+
+void LLAppViewer::saveExperienceCache()
+{
+ std::string filename =
+ gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml");
+ LL_INFOS("ExperienceCache") << "Saving " << filename << LL_ENDL;
+ llofstream cache_stream(filename);
+ if(cache_stream.is_open())
+ {
+ LLExperienceCache::exportFile(cache_stream);
+ }
+}
+
+void LLAppViewer::loadExperienceCache()
+{
+ std::string filename =
+ gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml");
+ LL_INFOS("ExperienceCache") << "Loading " << filename << LL_ENDL;
+ llifstream cache_stream(filename);
+ if(cache_stream.is_open())
+ {
+ LLExperienceCache::importFile(cache_stream);
+ }
+}
+
+
/*! @brief This class is an LLFrameTimer that can be created with
an elapsed time that starts counting up from the given value
rather than 0.0.
@@ -4372,7 +4399,7 @@ void LLAppViewer::idle()
// floating throughout the various object lists.
//
idleNameCache();
-
+ idleExperienceCache();
idleNetwork();
@@ -4786,6 +4813,22 @@ void LLAppViewer::idleNameCache()
LLAvatarNameCache::idle();
}
+void LLAppViewer::idleExperienceCache()
+{
+ LLViewerRegion* region = gAgent.getRegion();
+ if (!region) return;
+
+ std::string lookup_url=region->getCapability("GetExperienceInfo");
+ if(!lookup_url.empty() && lookup_url.back() != '/')
+ {
+ lookup_url += '/';
+ }
+
+ LLExperienceCache::setLookupURL(lookup_url);
+
+ LLExperienceCache::idle();
+}
+
//
// Handle messages, and all message related stuff
//
@@ -4949,6 +4992,7 @@ void LLAppViewer::disconnectViewer()
}
saveNameCache();
+ saveExperienceCache();
// close inventory interface, close all windows
LLFloaterInventory::cleanup();
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index 7a474f9122..4bf6bec0e8 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -114,6 +114,10 @@ public:
void loadNameCache();
void saveNameCache();
+ void loadExperienceCache();
+ void saveExperienceCache();
+
+
void removeMarkerFile(bool leave_logout_marker = false);
// LLAppViewer testing helpers.
@@ -218,6 +222,7 @@ private:
void idle();
void idleShutdown();
// update avatar SLID and display name caches
+ void idleExperienceCache();
void idleNameCache();
void idleNetwork();
diff --git a/indra/newview/llfloaterexperiences.cpp b/indra/newview/llfloaterexperiences.cpp
new file mode 100644
index 0000000000..b862b41bba
--- /dev/null
+++ b/indra/newview/llfloaterexperiences.cpp
@@ -0,0 +1,14 @@
+#include "llviewerprecompiledheaders.h"
+
+#include "llpanelexperiences.h"
+#include "llfloaterexperiences.h"
+
+LLFloaterExperiences::LLFloaterExperiences(const LLSD& data)
+ :LLFloater(data)
+{
+}
+
+BOOL LLFloaterExperiences::postBuild()
+{
+ return TRUE;
+}
diff --git a/indra/newview/llfloaterexperiences.h b/indra/newview/llfloaterexperiences.h
new file mode 100644
index 0000000000..1e5f216f8d
--- /dev/null
+++ b/indra/newview/llfloaterexperiences.h
@@ -0,0 +1,45 @@
+/**
+ * @file llfloaterexperiences.h
+ * @brief LLFloaterExperiences class definition
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATEREXPERIENCES_H
+#define LL_LLFLOATEREXPERIENCES_H
+
+#include "llfloater.h"
+
+class LLFloaterExperiences :
+ public LLFloater
+{
+public:
+ LLFloaterExperiences(const LLSD& data);
+
+protected:
+ /*virtual*/ BOOL postBuild();
+
+private:
+
+};
+
+#endif //LL_LLFLOATEREXPERIENCES_H
diff --git a/indra/newview/llpanelexperiences.cpp b/indra/newview/llpanelexperiences.cpp
new file mode 100644
index 0000000000..02a3c4c410
--- /dev/null
+++ b/indra/newview/llpanelexperiences.cpp
@@ -0,0 +1,302 @@
+#include "llviewerprecompiledheaders.h"
+
+
+#include "llpanelprofile.h"
+#include "lluictrlfactory.h"
+#include "llexperiencecache.h"
+#include "llagent.h"
+
+#include "llpanelexperiences.h"
+
+
+static LLRegisterPanelClassWrapper<LLPanelExperiences> register_experiences_panel("experiences_panel");
+
+
+LLPanelExperiences::LLPanelExperiences( )
+ : mExperiencesList(NULL),
+ mExperiencesAccTab(NULL),
+ mProfilePanel(NULL),
+ mPanelExperienceInfo(NULL),
+ mNoExperiences(false)
+{
+
+}
+
+void* LLPanelExperiences::create( void* data )
+{
+ return new LLPanelExperiences();
+}
+
+void ExperienceResult(LLHandle<LLPanelExperiences> panel, const LLSD& experience)
+{
+ LLPanelExperiences* experiencePanel = panel.get();
+ if(experiencePanel)
+ {
+ experiencePanel->addExperienceInfo(experience);
+ }
+}
+
+class LLExperienceListResponder : public LLHTTPClient::Responder
+{
+public:
+ LLExperienceListResponder(const LLHandle<LLPanelExperiences>& parent):mParent(parent)
+ {
+ }
+
+ LLHandle<LLPanelExperiences> mParent;
+
+ virtual void result(const LLSD& content)
+ {
+ if(mParent.isDead())
+ return;
+
+ LLSD experiences = content["experiences"];
+ LLSD::array_const_iterator it = experiences.beginArray();
+ for( /**/ ; it != experiences.endArray(); ++it)
+ {
+ LLUUID public_key = it->asUUID();
+
+ LLExperienceCache::get(public_key, LLExperienceCache::PUBLIC_KEY, boost::bind(ExperienceResult, mParent, _1));
+ }
+ }
+};
+
+void LLPanelExperiences::addExperienceInfo(const LLSD& experience)
+{
+ LLExperienceItem* item = new LLExperienceItem();
+ if(experience.has(LLExperienceCache::NAME))
+ {
+ item->setExperienceName(experience[LLExperienceCache::NAME].asString());
+ }
+ else if(experience.has("error"))
+ {
+ item->setExperienceName(experience["error"].asString());
+ }
+
+ if(experience.has(LLExperienceCache::PUBLIC_KEY))
+ {
+ item->setExperienceDescription(experience[LLExperienceCache::PUBLIC_KEY].asString());
+ }
+
+ mExperiencesList->addItem(item);
+
+}
+
+
+BOOL LLPanelExperiences::postBuild( void )
+{
+ mExperiencesList = getChild<LLFlatListView>("experiences_list");
+ if(hasString("no_experiences"))
+ {
+ mExperiencesList->setNoItemsCommentText(getString("no_experiences"));
+ }
+
+
+ LLViewerRegion* region = gAgent.getRegion();
+ if (region)
+ {
+ std::string lookup_url=region->getCapability("GetExperiences");
+ if(!lookup_url.empty())
+ {
+ LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLPanelExperiences>()));
+ }
+ }
+
+ mExperiencesAccTab = getChild<LLAccordionCtrlTab>("tab_experiences");
+ mExperiencesAccTab->setDropDownStateChangedCallback(boost::bind(&LLPanelExperiences::onAccordionStateChanged, this, mExperiencesAccTab));
+ mExperiencesAccTab->setDisplayChildren(true);
+
+ return TRUE;
+}
+
+void LLPanelExperiences::onOpen( const LLSD& key )
+{
+ LLPanel::onOpen(key);
+}
+
+void LLPanelExperiences::onClosePanel()
+{
+ if (mPanelExperienceInfo)
+ {
+ onPanelExperienceClose(mPanelExperienceInfo);
+ }
+}
+
+void LLPanelExperiences::updateData()
+{
+ if(isDirty())
+ {
+ mNoExperiences = false;
+ }
+}
+
+LLExperienceItem* LLPanelExperiences::getSelectedExperienceItem()
+{
+ LLPanel* selected_item = mExperiencesList->getSelectedItem();
+ if (!selected_item) return NULL;
+
+ return dynamic_cast<LLExperienceItem*>(selected_item);
+}
+
+void LLPanelExperiences::setProfilePanel( LLPanelProfile* profile_panel )
+{
+ mProfilePanel = profile_panel;
+}
+
+void LLPanelExperiences::onListCommit( const LLFlatListView* f_list )
+{
+ if(f_list == mExperiencesList)
+ {
+ mExperiencesList->resetSelection(true);
+ }
+ else
+ {
+ llwarns << "Unknown list" << llendl;
+ }
+
+ //updateButtons();
+}
+
+void LLPanelExperiences::onAccordionStateChanged( const LLAccordionCtrlTab* acc_tab )
+{
+ if(!mExperiencesAccTab->getDisplayChildren())
+ {
+ mExperiencesList->resetSelection(true);
+ }
+
+}
+
+void LLPanelExperiences::openExperienceInfo()
+{
+ LLSD selected_value = mExperiencesList->getSelectedValue();
+ if(selected_value.isUndefined())
+ {
+ return;
+ }
+
+ LLExperienceItem* experience = (LLExperienceItem*)mExperiencesList->getSelectedItem();
+
+ createExperienceInfoPanel();
+
+ LLSD params;
+ params["experience_name"] = experience->getExperienceName();
+ params["experience_desc"] = experience->getExperienceDescription();
+
+ getProfilePanel()->openPanel(mPanelExperienceInfo, params);
+
+}
+
+
+void LLPanelExperiences::createExperienceInfoPanel()
+{
+ if(!mPanelExperienceInfo)
+ {
+ mPanelExperienceInfo = LLPanelExperienceInfo::create();
+ mPanelExperienceInfo->setExitCallback(boost::bind(&LLPanelExperiences::onPanelExperienceClose, this, mPanelExperienceInfo));
+ mPanelExperienceInfo->setVisible(FALSE);
+ }
+}
+
+void LLPanelExperiences::onPanelExperienceClose( LLPanel* panel )
+{
+ getProfilePanel()->closePanel(panel);
+}
+
+LLPanelProfile* LLPanelExperiences::getProfilePanel()
+{
+ llassert_always(NULL != mProfilePanel);
+
+ return mProfilePanel;
+}
+
+
+
+
+
+
+
+
+
+
+
+LLExperienceItem::LLExperienceItem()
+{
+ buildFromFile("panel_experience_info.xml");
+}
+
+void LLExperienceItem::init( LLSD* experience_data )
+{
+ if(experience_data)
+ {
+ setExperienceDescription(experience_data->has(LLExperienceCache::PUBLIC_KEY)?(*experience_data)[LLExperienceCache::PUBLIC_KEY].asString() : std::string());
+ setExperienceName(experience_data->has(LLExperienceCache::NAME)?(*experience_data)[LLExperienceCache::NAME].asString() : std::string());
+ }
+}
+
+void LLExperienceItem::setExperienceDescription( const std::string& val )
+{
+ mExperienceDescription = val;
+ getChild<LLUICtrl>("experience_desc")->setValue(val);
+}
+
+void LLExperienceItem::setExperienceName( const std::string& val )
+{
+ mExperienceName = val;
+ getChild<LLUICtrl>("experience_name")->setValue(val);
+}
+
+BOOL LLExperienceItem::postBuild()
+{
+ return TRUE;
+}
+
+void LLExperienceItem::update()
+{
+
+}
+
+void LLExperienceItem::processProperties( void* data, EAvatarProcessorType type )
+{
+
+}
+
+LLExperienceItem::~LLExperienceItem()
+{
+
+}
+
+
+void LLPanelExperienceInfo::setExperienceName( const std::string& name )
+{
+ getChild<LLUICtrl>("experience_name")->setValue(name);
+}
+
+void LLPanelExperienceInfo::setExperienceDesc( const std::string& desc )
+{
+ getChild<LLUICtrl>("experience_desc")->setValue(desc);
+}
+
+void LLPanelExperienceInfo::onOpen( const LLSD& key )
+{
+ setExperienceName(key["experience_name"]);
+ setExperienceDesc(key["experience_desc"]);
+
+ /*
+ LLAvatarPropertiesProcessor::getInstance()->addObserver(
+ getAvatarId(), this);
+ LLAvatarPropertiesProcessor::getInstance()->sendPickInfoRequest(
+ getAvatarId(), getPickId());
+ */
+}
+
+LLPanelExperienceInfo* LLPanelExperienceInfo::create()
+{
+ LLPanelExperienceInfo* panel = new LLPanelExperienceInfo();
+ panel->buildFromFile("panel_experience_info.xml");
+ return panel;
+}
+
+void LLPanelExperienceInfo::setExitCallback( const commit_callback_t& cb )
+{
+ getChild<LLButton>("back_btn")->setClickedCallback(cb);
+}
diff --git a/indra/newview/llpanelexperiences.h b/indra/newview/llpanelexperiences.h
new file mode 100644
index 0000000000..1fe3f6ae1d
--- /dev/null
+++ b/indra/newview/llpanelexperiences.h
@@ -0,0 +1,119 @@
+/**
+ * @file llpanelpicks.h
+ * @brief LLPanelPicks and related class definitions
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLPANELEXPERIENCES_H
+#define LL_LLPANELEXPERIENCES_H
+
+#include "llaccordionctrltab.h"
+#include "llflatlistview.h"
+#include "llpanelavatar.h"
+
+class LLExperienceItem;
+class LLPanelProfile;
+
+class LLPanelExperienceInfo
+ : public LLPanel
+{
+public:
+ static LLPanelExperienceInfo* create();
+
+ void onOpen(const LLSD& key);
+ void setExperienceName( const std::string& name );
+ void setExperienceDesc( const std::string& desc );
+
+
+ virtual void setExitCallback(const commit_callback_t& cb);
+};
+
+
+class LLPanelExperiences
+ : public LLPanel /*LLPanelProfileTab*/
+{
+public:
+ LLPanelExperiences();
+
+ static void* create(void* data);
+
+ /*virtual*/ BOOL postBuild(void);
+
+ /*virtual*/ void onOpen(const LLSD& key);
+
+ /*virtual*/ void onClosePanel();
+
+ void updateData();
+
+ LLExperienceItem* getSelectedExperienceItem();
+
+ void setProfilePanel(LLPanelProfile* profile_panel);
+ void addExperienceInfo(const LLSD& experience);
+protected:
+
+ void onListCommit(const LLFlatListView* f_list);
+ void onAccordionStateChanged(const LLAccordionCtrlTab* acc_tab);
+
+
+ void openExperienceInfo();
+ void createExperienceInfoPanel();
+ void onPanelExperienceClose(LLPanel* panel);
+ LLPanelProfile* getProfilePanel();
+private:
+ LLFlatListView* mExperiencesList;
+ LLAccordionCtrlTab* mExperiencesAccTab;
+ LLPanelProfile* mProfilePanel;
+ LLPanelExperienceInfo* mPanelExperienceInfo;
+ bool mNoExperiences;
+};
+
+
+class LLExperienceItem
+ : public LLPanel
+ //, public LLAvatarPropertiesObserver
+{
+public:
+ LLExperienceItem();
+ ~LLExperienceItem();
+
+ void init(LLSD* experience_data);
+ /*virtual*/ BOOL postBuild();
+ void update();
+
+ /*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
+
+ void setCreatorID(const LLUUID& val) { mCreatorID = val; }
+ void setExperienceDescription(const std::string& val);
+ void setExperienceName(const std::string& val);
+
+ const LLUUID& getCreatorID() const { return mCreatorID; }
+ const std::string& getExperienceName() const { return mExperienceName; }
+ const std::string& getExperienceDescription() const { return mExperienceDescription; }
+
+protected:
+ LLUUID mCreatorID;
+
+ std::string mExperienceName;
+ std::string mExperienceDescription;
+};
+#endif // LL_LLPANELEXPERIENCES_H
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 3d1fd74ba6..a58a26b6fe 100755
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -47,6 +47,7 @@
#include "llares.h"
#include "llavatarnamecache.h"
+#include "llexperiencecache.h"
#include "lllandmark.h"
#include "llcachename.h"
#include "lldir.h"
@@ -1398,6 +1399,9 @@ bool idle_startup()
LLStartUp::initNameCache();
display_startup();
+ LLStartUp::initExperienceCache();
+ display_startup();
+
// update the voice settings *after* gCacheName initialization
// so that we can construct voice UI that relies on the name cache
LLVoiceClient::getInstance()->updateSettings();
@@ -2809,6 +2813,13 @@ void LLStartUp::initNameCache()
LLAvatarNameCache::setUseDisplayNames(gSavedSettings.getBOOL("UseDisplayNames"));
}
+
+void LLStartUp::initExperienceCache()
+{
+ LLAppViewer::instance()->loadExperienceCache();
+ LLExperienceCache::initClass();
+}
+
void LLStartUp::cleanupNameCache()
{
LLAvatarNameCache::cleanupClass();
@@ -3504,3 +3515,4 @@ void transition_back_to_login_panel(const std::string& emsg)
reset_login(); // calls LLStartUp::setStartupState( STATE_LOGIN_SHOW );
gSavedSettings.setBOOL("AutoLogin", FALSE);
}
+
diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h
index 760e38890b..00e03bcda6 100755
--- a/indra/newview/llstartup.h
+++ b/indra/newview/llstartup.h
@@ -91,6 +91,7 @@ public:
static void fontInit();
static void initNameCache();
+ static void initExperienceCache();
static void cleanupNameCache();
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 1f7cf0cdd4..5582d256f8 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -56,6 +56,7 @@
#include "llfloatereditsky.h"
#include "llfloatereditwater.h"
#include "llfloaterenvironmentsettings.h"
+#include "llfloaterexperiences.h"
#include "llfloaterevent.h"
#include "llfloaterdestinations.h"
#include "llfloaterfonttest.h"
@@ -204,7 +205,8 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("env_edit_day_cycle", "floater_edit_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEditDayCycle>);
LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEvent>);
-
+ LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiences>);
+
LLFloaterReg::add("font_test", "floater_font_test.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFontTest>);
LLFloaterReg::add("gestures", "floater_gesture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGesture>);
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index b607afbd9d..7417e4fa06 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -1536,6 +1536,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
}
capabilityNames.append("GetDisplayNames");
+ capabilityNames.append("GetExperiences");
+ capabilityNames.append("GetExperienceInfo");
capabilityNames.append("GetMesh");
capabilityNames.append("GetObjectCost");
capabilityNames.append("GetObjectPhysicsData");
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 627238b0f5..2d89db6e28 100755
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -48,6 +48,7 @@
#include "llanimationstates.h"
#include "llavatarnamecache.h"
#include "llavatarpropertiesprocessor.h"
+#include "llexperiencecache.h"
#include "llphysicsmotion.h"
#include "llviewercontrol.h"
#include "llcallingcard.h" // IDEVO for LLAvatarTracker
@@ -2516,7 +2517,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time)
idleUpdateBelowWater(); // wind effect uses this
idleUpdateWindEffect();
}
-
+
idleUpdateNameTag( root_pos_last );
idleUpdateRenderCost();
}
diff --git a/indra/newview/skins/default/xui/en/floater_experiences.xml b/indra/newview/skins/default/xui/en/floater_experiences.xml
new file mode 100644
index 0000000000..57541c8b35
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_experiences.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+
+<floater
+ positioning="cascading"
+ can_close="true"
+ can_resize="true"
+ height="400"
+ help_topic="sidebar_experiences"
+ min_height="300"
+ min_width="300"
+ layout="topleft"
+ name="floater_experiences"
+ save_rect="false"
+ single_instance="true"
+ reuse_instance="false"
+ title="EXPERIENCES"
+ width="400">
+ <panel
+ top="3"
+ left="3"
+ layout="topleft"
+ right="-3"
+ follows="all"
+ height="300"
+ class="experiences_panel"
+ filename="panel_experiences.xml"
+ >
+ </panel>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 18932a32d0..9b24460ddf 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -53,6 +53,14 @@
parameter="" />
</menu_item_call>
<menu_item_call
+ label="Experiences..."
+ name="Experiences"
+ shortcut="control|E">
+ <menu_item_call.on_click
+ function="Floater.ToggleOrBringToFront"
+ parameter="experiences"/>
+ </menu_item_call>
+ <menu_item_call
label="Places..."
name="Places">
<menu_item_call.on_click
diff --git a/indra/newview/skins/default/xui/en/panel_experience_info.xml b/indra/newview/skins/default/xui/en/panel_experience_info.xml
new file mode 100644
index 0000000000..47f366d857
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_experience_info.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ fit_parent="true"
+ follows="all"
+ height="120"
+ label="Experiences"
+ layout="topleft"
+ left="0"
+ name="panel_experience_info"
+ top_pad="0">
+ <text
+ follows="top|left|right"
+ font="SansSerifHugeBold"
+ height="26"
+ layout="topleft"
+ left_pad="4"
+ name="title"
+ text_color="White"
+ top="2"
+ value="Experience Info"
+ use_ellipses="true"
+ right="-3"/>
+ <text
+ follows="top|left|right"
+ font="SansSerifBig"
+ height="20"
+ layout="topleft"
+ left_pad="4"
+ name="name_label"
+ text_color="White"
+ left="8"
+ top_delta="28"
+ value="Name"
+ use_ellipses="true"
+ right="-3" />
+ <text
+ follows="top|left|right"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left_pad="8"
+ name="experience_name"
+ text_color="White"
+ left="16"
+ top_delta="22"
+ value="[loading...]"
+ use_ellipses="true"
+ right="-3" />
+ <text
+ follows="top|left|right"
+ font="SansSerifBig"
+ height="20"
+ left="8"
+ layout="topleft"
+ left_pad="4"
+ name="desc_label"
+ text_color="White"
+ top_delta="22"
+ value="Description"
+ use_ellipses="true"
+ right="-3" />
+ <expandable_text
+ follows="top|left|right"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left_pad="8"
+ name="experience_desc"
+ text_color="White"
+ left="16"
+ top_delta="22"
+ value="[loading...]"
+ use_ellipses="true"
+ right="-3"
+ word_wrap="true" />
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_experiences.xml b/indra/newview/skins/default/xui/en/panel_experiences.xml
new file mode 100644
index 0000000000..47a3005aae
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_experiences.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+
+<panel
+ layout="topleft"
+ top="3"
+ left="3"
+ right="-3"
+ bottom="-3"
+ label="Experiences"
+ follows="all">
+ <string
+ name="no_experiences"
+ value="No experiences."/>
+
+ <accordion
+ fit_parent="true"
+ layout="topleft"
+ top="0"
+ left="3"
+ right="-3"
+ bottom="-3"
+ single_expansion="true"
+ follows="all">
+ <accordion_tab
+ name="tab_experiences"
+ layout="topleft"
+ top="0"
+ left="0"
+ right="-3"
+ title="Experiences"
+ follows="all">
+ <flat_list_view
+ name="experiences_list"
+ layout="topleft"
+ top="0"
+ left="0"
+ follows="all"/>
+ </accordion_tab>
+ </accordion>
+</panel>