diff options
26 files changed, 1675 insertions, 25 deletions
| @@ -1,6 +1,5 @@  syntax: glob -  # WinMerge temp files  *.bak  # Compiled python bytecode diff --git a/indra/llcommon/roles_constants.h b/indra/llcommon/roles_constants.h index effd15ea72..65ec290200 100755 --- a/indra/llcommon/roles_constants.h +++ b/indra/llcommon/roles_constants.h @@ -53,7 +53,7 @@ enum LLRoleChangeType  // KNOWN HOLES: use these for any single bit powers you need  // bit 0x1 << 46 -// bit 0x1 << 49 and above +// bit 0x1 << 51 and above  // These powers were removed to make group roles simpler  // bit 0x1 << 41 (GP_ACCOUNTING_VIEW) @@ -146,6 +146,9 @@ const U64 GP_SESSION_JOIN = 0x1LL << 16; //can join session  const U64 GP_SESSION_VOICE = 0x1LL << 27; //can hear/talk  const U64 GP_SESSION_MODERATOR = 0x1LL << 37; //can mute people's session +const U64 GP_EXPERIENCE_ADMIN	= 0x1LL << 49; // has admin rights to any experiences owned by this group +const U64 GP_EXPERIENCE_CREATOR = 0x1LL << 50; // can sign scripts for experiences owned by this group +  const U64 GP_DEFAULT_MEMBER = GP_ACCOUNTING_ACCOUNTABLE  								| GP_LAND_ALLOW_SET_HOME  								| GP_NOTICES_RECEIVE diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index d193e367eb..5740584edb 100755 --- 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..219e68b51c --- /dev/null +++ b/indra/llmessage/llexperiencecache.cpp @@ -0,0 +1,650 @@ +/**  + * @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 +{ + +	typedef std::map<LLUUID, LLUUID> PrivateKeyMap; +	PrivateKeyMap experinceKeyMap; +	 +	void mapPrivateKeys(const LLSD& legacyKeys); + + +	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(EXPERIENCE_ID)) +		{ +			sPendingQueue.erase(row[EXPERIENCE_ID].asUUID()); +		} + +		if(row.has(OWNER_ID)) +		{ +			sPendingQueue.erase(row[OWNER_ID].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; +	} + +	void bootstrap(const LLSD& legacyKeys, int initialExpiration) +	{ +		mapPrivateKeys(legacyKeys); +		LLSD::array_const_iterator it = legacyKeys.beginArray(); +		for(/**/; it != legacyKeys.endArray(); ++it) +		{ +			LLSD experience = *it; +			if(experience.has(EXPERIENCE_ID)) +			{ +				if(!experience.has(EXPIRES)) +				{ +					experience[EXPIRES] = initialExpiration; +				} +				processExperience(experience[EXPERIENCE_ID].asUUID(), experience); +			} +			else +			{ +				LL_WARNS("ExperienceCache")  +					<< "Skipping bootstrap entry which is missing " << EXPERIENCE_ID  +					<< LL_ENDL; +			} +		}		 +	} + + + +	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 EXPIRES 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(EXPERIENCE_ID) || it->second[EXPERIENCE_ID].asUUID().isNull() || +				it->second.has("DoesNotExist") || (it->second.has(PROPERTIES) && it->second[PROPERTIES].asInteger() & PROPERTY_INVALID)) +				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[EXPERIENCE_ID].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::array_const_iterator errIt = error_ids.beginArray(); +			for( /**/ ; errIt != error_ids.endArray() ; ++errIt ) +			{ +				LLUUID id = errIt->asUUID();		 +				LLSD exp; +				exp[EXPIRES]=DEFAULT_EXPIRATION; +				exp[EXPERIENCE_ID] = id; +				exp[PROPERTIES]=PROPERTY_INVALID; +				exp["DoesNotExist"]=true; + +				processExperience(id, exp); +				LL_INFOS("ExperienceCache") << "Error result for " << id << LL_ENDL ; +			} + +			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[EXPERIENCE_ID] = it->first; +				exp["key_type"] = it->second; +				exp["uuid"] = it->first; +				exp["error"] = (LLSD::Integer)status; +				exp[PROPERTIES]=PROPERTY_INVALID; + 				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(); +		} +	} + +	void erase( const LLUUID& key ) +	{ +		cache_t::iterator it = sCache.find(key); +				 +		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(EXPERIENCE_ID)) +				{ +					LLUUID id = exp[EXPERIENCE_ID].asUUID(); +					S32 properties = PROPERTY_INVALID; +					if(exp.has(PROPERTIES)) +					{ +						properties = exp[PROPERTIES].asInteger(); +					} +					if(id.notNull() && ((properties & PROPERTY_INVALID) == 0)) +					{ +						fetch(id, true); +					} +					else +					{ +						sCache.erase(cur); +					} +				} +			} +		} +	} + +	 +	bool fetch( const LLUUID& key, bool refresh/* = true*/ )  +	{ +		if(!key.isNull() && !isRequestPending(key) && (refresh || sCache.find(key)==sCache.end())) +		{ +			LL_INFOS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL ; +			sAskQueue[key]=EXPERIENCE_ID; + +			return true; +		} +		return false; +	} + +	void insert(const LLSD& experience_data ) +	{ +		if(experience_data.has(EXPERIENCE_ID)) +		{ +			sCache[experience_data[EXPERIENCE_ID].asUUID()]=experience_data; +		} +		else +		{ +			LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << EXPERIENCE_ID << LL_ENDL; +		} +	} + +	bool get( const LLUUID& key, LLSD& experience_data ) +	{ +		if(key.isNull()) return false; +		cache_t::const_iterator it = sCache.find(key); +	 +		if (it != sCache.end()) +		{ +			experience_data = it->second; +			return true; +		} +		 +		fetch(key); + +		return false; +	} + + +	void get( const LLUUID& key, callback_slot_t slot ) +	{ +		if(key.isNull()) return; + +		cache_t::const_iterator it = sCache.find(key); +		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); + +		// 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); +		} +	} + +} + + + +void LLExperienceCache::mapPrivateKeys( const LLSD& legacyKeys ) +{ +	LLSD::array_const_iterator exp = legacyKeys.beginArray(); +	for(/**/ ; exp != legacyKeys.endArray() ; ++exp) +	{ +		if(exp->has(LLExperienceCache::EXPERIENCE_ID) && exp->has(LLExperienceCache::PRIVATE_KEY)) +		{ +			experinceKeyMap[(*exp)[LLExperienceCache::PRIVATE_KEY].asUUID()]=(*exp)[LLExperienceCache::EXPERIENCE_ID].asUUID(); +		} +	} +} + + +LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_if_not_found) +{ +	if (private_key.isNull()) +		return LLUUID::null; + + +	PrivateKeyMap::const_iterator it=experinceKeyMap.find(private_key); +	if(it == experinceKeyMap.end()) +	{ +		if(null_if_not_found) +		{ +			return LLUUID::null; +		} +		return private_key; +	} +	LL_WARNS("LLExperience") << "converted private key " << private_key << " to experience_id " << it->second << LL_ENDL; +	return it->second; +} diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h new file mode 100644 index 0000000000..fb00ea31f0 --- /dev/null +++ b/indra/llmessage/llexperiencecache.h @@ -0,0 +1,94 @@ +/**  + * @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 PRIVATE_KEY	= "private_id"; + +	const std::string EXPERIENCE_ID	= "public_id"; +	const std::string OWNER_ID		= "owner_id"; +	const std::string NAME			= "name"; +	const std::string PROPERTIES	= "properties"; +	const std::string EXPIRES		= "expiration";   +	const std::string DESCRIPTION	= "description"; + +	// should be in sync with experience-api/experiences/models.py +	const int PROPERTY_INVALID		= 1 << 0; +	const int PROPERTY_PRIVILEGED	= 1 << 3; +	const int PROPERTY_GRID			= 1 << 4; +	const int PROPERTY_PRIVATE		= 1 << 5; +	const int PROPERTY_DISABLED		= 1 << 6;   +	const int PROPERTY_SUSPENDED	= 1 << 7; + + +	const static F64 DEFAULT_EXPIRATION = 600.0; + +	// 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 bootstrap(const LLSD& legacyKeys, int initialExpiration); +	 +	void erase(const LLUUID& key); +	bool fetch(const LLUUID& key, bool refresh=false); +	void insert(LLSD& experience_data); +	bool get(const LLUUID& key, LLSD& experience_data); + +	// If name information is in cache, callback will be called immediately. +	void get(const LLUUID& key, callback_slot_t slot); + +	const cache_t& getCached(); + +	LLUUID getExperienceId(const LLUUID& private_key, bool null_if_not_found=false); + +}; + +#endif // LL_LLEXPERIENCECACHE_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d06cee5ee6..e7c62d7ee9 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -226,6 +226,7 @@ set(viewer_SOURCE_FILES      llfloatereditwater.cpp      llfloaterenvironmentsettings.cpp      llfloaterevent.cpp +    llfloaterexperiences.cpp      llfloaterfonttest.cpp      llfloatergesture.cpp      llfloatergodtools.cpp @@ -395,6 +396,7 @@ set(viewer_SOURCE_FILES      llpanelclassified.cpp      llpanelcontents.cpp      llpaneleditwearable.cpp +    llpanelexperiences.cpp      llpanelface.cpp      llpanelgenerictip.cpp      llpanelgroup.cpp @@ -809,6 +811,7 @@ set(viewer_HEADER_FILES      llfloatereditwater.h      llfloaterenvironmentsettings.h      llfloaterevent.h +    llfloaterexperiences.h      llfloaterfonttest.h      llfloatergesture.h      llfloatergodtools.h @@ -971,6 +974,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 f92274dbbd..912a5e9b7f 100755 --- 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" @@ -4409,7 +4410,7 @@ void LLAppViewer::loadNameCache()  }  void LLAppViewer::saveNameCache() -	{ +{  	// display names cache  	std::string filename =  		gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); @@ -4417,7 +4418,7 @@ void LLAppViewer::saveNameCache()  	if(name_cache_stream.is_open())  	{  		LLAvatarNameCache::exportFile(name_cache_stream); -} +	}  	if (!gCacheName) return; @@ -4430,6 +4431,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. @@ -4629,7 +4656,7 @@ void LLAppViewer::idle()  	    // floating throughout the various object lists.  	    //  		idleNameCache(); -     +		idleExperienceCache();  		idleNetwork(); @@ -5049,6 +5076,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.rbegin() != '/') +	{ +		lookup_url += '/'; +	} +	 +	LLExperienceCache::setLookupURL(lookup_url); + +	LLExperienceCache::idle(); +} +  //  // Handle messages, and all message related stuff  // @@ -5211,6 +5254,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 cd91ae8b2b..260eb7352e 100755 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -117,6 +117,10 @@ public:      void loadNameCache();      void saveNameCache(); +	void loadExperienceCache(); +	void saveExperienceCache(); + +  	void removeMarkerFile(bool leave_logout_marker = false);      // LLAppViewer testing helpers. @@ -222,6 +226,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..617ceef615 --- /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, 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::DESCRIPTION)) +	{ +		item->setExperienceDescription(experience[LLExperienceCache::DESCRIPTION].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::DESCRIPTION)?(*experience_data)[LLExperienceCache::DESCRIPTION].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/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 968a912ea2..bba0f1330c 100755 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -86,6 +86,7 @@  #include "lltrans.h"  #include "llviewercontrol.h"  #include "llappviewer.h" +#include "llexperiencecache.h"  const std::string HELLO_LSL =  	"default\n" @@ -374,12 +375,21 @@ LLScriptEdCore::~LLScriptEdCore()  	delete mLiveFile;  } +void LLScriptEdCore::experienceChanged() +{ +	enableSave(TRUE); +	getChildView("Save_btn")->setEnabled(true); +} +  BOOL LLScriptEdCore::postBuild()  {  	mErrorList = getChild<LLScrollListCtrl>("lsl errors");  	mFunctions = getChild<LLComboBox>( "Insert..."); +	mExperiences = getChild<LLComboBox>("Experiences..."); +	mExperiences->setCommitCallback(boost::bind(&LLScriptEdCore::experienceChanged, this)); +  	childSetCommitCallback("Insert...", &LLScriptEdCore::onBtnInsertFunction, this);  	mEditor = getChild<LLViewerTextEditor>("Script Editor"); @@ -389,6 +399,10 @@ BOOL LLScriptEdCore::postBuild()  	childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this));  	initMenu(); +	 + + +	requestExperiences();  	std::vector<std::string> funcs; @@ -1191,6 +1205,125 @@ bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata)  	return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : FALSE;  } + +void AddExperienceResult(LLHandle<LLScriptEdCore> panel, const LLSD& experience) +{ +	LLScriptEdCore* scriptCore = panel.get(); +	if(scriptCore) +	{ +		scriptCore->addExperienceInfo(experience); +	} +} + + +class ExperienceResponder : public LLHTTPClient::Responder +{ +public: +	ExperienceResponder(const LLHandle<LLScriptEdCore>& parent):mParent(parent) +	{ +	} + +	LLHandle<LLScriptEdCore> mParent; + +	virtual void result(const LLSD& content) +	{ +		LLScriptEdCore* scriptCore = mParent.get(); +		if(!scriptCore) +			return; + +		scriptCore->clearExperiences(); + +		LLSD experiences = content["experience_ids"]; +		LLSD::array_const_iterator it = experiences.beginArray(); +		for( /**/ ; it != experiences.endArray(); ++it) +		{ +			LLUUID public_key = it->asUUID(); + +			LLExperienceCache::get(public_key, boost::bind(AddExperienceResult, mParent, _1)); +		} +	} +}; + +class ExperienceAssociationResponder : public LLHTTPClient::Responder +{ +public: +    ExperienceAssociationResponder(const LLUUID& parent):mParent(parent) +    { +    } + +    LLUUID mParent; + +    virtual void result(const LLSD& content) +    { + +        LLLiveLSLEditor* scriptCore = LLFloaterReg::findTypedInstance<LLLiveLSLEditor>("preview_scriptedit", mParent); + +        if(!scriptCore || !content.has("experience")) +            return; + +        scriptCore->setAssociatedExperience(content["experience"].asUUID()); +    } + +    virtual void error(U32 status, const std::string& reason) +    { +        llinfos << "Failed to look up associated script: " << status << ": " << reason << llendl; +    } + +}; + +void LLScriptEdCore::requestExperiences() +{ +	mExperiences->setEnabled(FALSE); + +	LLViewerRegion* region = gAgent.getRegion(); +	if (region) +	{ +		std::string lookup_url=region->getCapability("GetCreatorExperiences");  +		if(!lookup_url.empty()) +		{ +			LLHTTPClient::get(lookup_url, new ExperienceResponder(getDerivedHandle<LLScriptEdCore>())); +		} +	} +} + +void LLScriptEdCore::addExperienceInfo( const LLSD& experience ) +{ +	mExperiences->setEnabled(TRUE); +	mExperiences->add(experience[LLExperienceCache::NAME], experience[LLExperienceCache::EXPERIENCE_ID].asUUID()); +    if(mAssociatedExperience == experience[LLExperienceCache::EXPERIENCE_ID].asUUID()) +    { +        setAssociatedExperience(mAssociatedExperience); +    } +} + +void LLScriptEdCore::clearExperiences() +{ +	mExperiences->removeall(); +	mExperiences->add("No Experience", LLUUID::null); +} + +LLUUID LLScriptEdCore::getSelectedExperience()const +{ +	return mExperiences->getSelectedValue().asUUID(); +} + +void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) +{ +    mAssociatedExperience = experience_id; +    if(experience_id.notNull()) +    { +        if(!mExperiences->setSelectedByValue(mAssociatedExperience, TRUE)) +        { +            mExperiences->setSelectedByValue(LLUUID::null, TRUE); +        } +    } +} + + + + + +  /// ---------------------------------------------------------------------------  /// LLScriptEdContainer  /// --------------------------------------------------------------------------- @@ -1860,7 +1993,7 @@ void LLLiveLSLEditor::loadAsset()  			LLHost host(object->getRegion()->getIP(),  						object->getRegion()->getPort());  			gMessageSystem->sendReliable(host); -			*/ +			*/             		}  	}  	else @@ -1893,11 +2026,15 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id,  	lldebugs << "LLLiveLSLEditor::onLoadComplete: got uuid " << asset_id  		 << llendl;  	LLUUID* xored_id = (LLUUID*)user_data; -	 + +     	LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance<LLLiveLSLEditor>("preview_scriptedit", *xored_id);  	if(instance )  	{ +        instance->fetchAssociatedExperience(asset_id); + +  		if( LL_ERR_NOERR == status )  		{  			instance->loadScriptText(vfs, asset_id, type); @@ -2146,8 +2283,8 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/)  	mPendingUploads++;  	BOOL is_running = getChild<LLCheckBoxCtrl>( "running")->get();  	if (!url.empty()) -	{ -		uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running); +	{		 +		uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running, mScriptEd->getSelectedExperience());  	}  	else if (gAssetStorage)  	{ @@ -2155,11 +2292,7 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/)  	}  } -void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url, -										 const std::string& filename, -										 const LLUUID& task_id, -										 const LLUUID& item_id, -										 BOOL is_running) +void LLLiveLSLEditor::uploadAssetViaCaps( const std::string& url, const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, BOOL is_running, const LLUUID& experience_public_id )  {  	llinfos << "Update Task Inventory via capability " << url << llendl;  	LLSD body; @@ -2167,6 +2300,7 @@ void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url,  	body["item_id"] = item_id;  	body["is_script_running"] = is_running;  	body["target"] = monoChecked() ? "mono" : "lsl2"; +	body["experience"] = experience_public_id;  	LLHTTPClient::post(url, body,  		new LLUpdateTaskInventoryResponder(body, filename, LLAssetType::AT_LSL_TEXT));  } @@ -2347,6 +2481,25 @@ void LLLiveLSLEditor::onSaveBytecodeComplete(const LLUUID& asset_uuid, void* use  	delete data;  } +void LLLiveLSLEditor::fetchAssociatedExperience(const LLUUID& asset_id) +{ +    LLViewerRegion* region = gAgent.getRegion(); +    if (region) +    { +        std::string lookup_url=region->getCapability("GetMetadata");  +        if(!lookup_url.empty()) +        { +            LLSD request; +            request["asset-id"]=asset_id; +            LLSD fields; +            fields.append("experience"); +            request["fields"] = fields; +            LLHTTPClient::post(lookup_url, request, new ExperienceAssociationResponder(getKey())); +        } +    } +} + +  BOOL LLLiveLSLEditor::canClose()  {  	return (mScriptEd->canClose()); @@ -2418,3 +2571,11 @@ BOOL LLLiveLSLEditor::monoChecked() const  	}  	return FALSE;  } + +void LLLiveLSLEditor::setAssociatedExperience( const LLUUID& experience_id ) +{ +    if(mScriptEd) +    { +        mScriptEd->setAssociatedExperience(experience_id); +    } +} diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 7563cecd9d..28e71b111a 100755 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -106,6 +106,10 @@ public:  	static bool		enableLoadFromFileMenu(void* userdata);  	virtual bool	hasAccelerators() const { return true; } +	void			addExperienceInfo( const LLSD& experience ); +	void			clearExperiences(); +    LLUUID 			getSelectedExperience()const; +    void            setAssociatedExperience( const LLUUID& experience_id );  private:  	void		onBtnHelp(); @@ -120,6 +124,9 @@ private:  	void enableSave(BOOL b) {mEnableSave = b;} +	void requestExperiences(); +	void experienceChanged(); +  protected:  	void deleteBridges();  	void setHelpPage(const std::string& help_string); @@ -133,8 +140,9 @@ private:  	void			(*mLoadCallback)(void* userdata);  	void			(*mSaveCallback)(void* userdata, BOOL close_after_save);  	void			(*mSearchReplaceCallback) (void* userdata); -	void*			mUserdata; +    void*			mUserdata;  	LLComboBox		*mFunctions; +	LLComboBox		*mExperiences;  	BOOL			mForceClose;  	LLPanel*		mCodePanel;  	LLScrollListCtrl* mErrorList; @@ -146,6 +154,7 @@ private:  	BOOL			mEnableSave;  	BOOL			mHasScriptData;  	LLLiveLSLFile*	mLiveFile; +    LLUUID          mAssociatedExperience;  	LLScriptEdContainer* mContainer; // parent view  }; @@ -227,7 +236,9 @@ public:  	/*virtual*/ BOOL postBuild(); -	void setIsNew() { mIsNew = TRUE; } +    void setIsNew() { mIsNew = TRUE; } +    void setAssociatedExperience( const LLUUID& experience_id ); +    void fetchAssociatedExperience(const LLUUID& asset_id);  private:  	virtual BOOL canClose(); @@ -237,11 +248,7 @@ private:  	virtual void loadAsset();  	void loadAsset(BOOL is_new);  	/*virtual*/ void saveIfNeeded(bool sync = true); -	void uploadAssetViaCaps(const std::string& url, -							const std::string& filename,  -							const LLUUID& task_id, -							const LLUUID& item_id, -							BOOL is_running); +	void uploadAssetViaCaps(const std::string& url, const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, BOOL is_running, const LLUUID& experience_public_id);  	void uploadAssetLegacy(const std::string& filename,  						   LLViewerObject* object,  						   const LLTransactionID& tid, @@ -282,7 +289,7 @@ private:  	S32					mPendingUploads;  	BOOL getIsModifiable() const { return mIsModifiable; } // Evaluated on load assert -	 +  	LLCheckBoxCtrl*	mMonoCheckbox;  	BOOL mIsModifiable;  }; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index cff3a7e02a..3f8f5e282a 100755 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,6 +48,7 @@  #include "llares.h"  #include "llavatarnamecache.h" +#include "llexperiencecache.h"  #include "lllandmark.h"  #include "llcachename.h"  #include "lldir.h" @@ -1401,6 +1402,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(); @@ -2814,6 +2818,13 @@ void LLStartUp::initNameCache()  	LLAvatarNameCache::setUseDisplayNames(gSavedSettings.getBOOL("UseDisplayNames"));  } + +void LLStartUp::initExperienceCache() +{ +	LLAppViewer::instance()->loadExperienceCache(); +	LLExperienceCache::initClass(); +} +  void LLStartUp::cleanupNameCache()  {  	LLAvatarNameCache::cleanupClass(); @@ -3510,3 +3521,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 c6b28b9e5e..c6296a20d7 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -58,6 +58,7 @@  #include "llfloatereditsky.h"  #include "llfloatereditwater.h"  #include "llfloaterenvironmentsettings.h" +#include "llfloaterexperiences.h"  #include "llfloaterevent.h"  #include "llfloaterdestinations.h"  #include "llfloaterfonttest.h" @@ -207,7 +208,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 8422708add..adc346529e 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1596,7 +1596,11 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)  	}  	capabilityNames.append("GetDisplayNames"); -	capabilityNames.append("GetMesh"); +	capabilityNames.append("GetExperiences"); +	capabilityNames.append("GetExperienceInfo"); +	capabilityNames.append("GetCreatorExperiences"); +    capabilityNames.append("GetMesh"); +    capabilityNames.append("GetMetadata");  	capabilityNames.append("GetObjectCost");  	capabilityNames.append("GetObjectPhysicsData");  	capabilityNames.append("GetTexture"); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 1a050800b4..15d5dd63c7 100755 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -43,6 +43,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 @@ -2037,7 +2038,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 b01c3067ff..752734d7bd 100755 --- 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> diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml index 765b07ed8b..7e4ac1d7fb 100755 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -204,4 +204,13 @@       right="400"       name="Edit_btn"       width="81" /> +    <combo_box +      follows="right|bottom" +      height="23" +      label="Experience..." +      layout="topleft" +      name="Experiences..." +      width="196" +      right="487" +      top_pad="5" />  </panel> diff --git a/indra/newview/skins/default/xui/en/role_actions.xml b/indra/newview/skins/default/xui/en/role_actions.xml index 89aef57cca..65acc14b6a 100755 --- a/indra/newview/skins/default/xui/en/role_actions.xml +++ b/indra/newview/skins/default/xui/en/role_actions.xml @@ -187,4 +187,16 @@  		     longdescription="Members in a Role with this Ability can control access and participation in group voice and text chat sessions."  		     name="moderate group chat" value="37" />  	</action_set> +  <action_set +    description="These Abilities include power to modify experiences owned by this group." +    name="experience_tools_experience"> +    <action description="Experience Admin" +            longdescription="Members in a role with this ability can edit the meta-data for an experience." +            name="experience admin" +            value ="49" /> +    <action description="Experience Creator" +            longdescription="Members in a role with this ability can create scripts for an experience." +            name="experience creator" +            value ="50" /> +  </action_set>  </role_actions> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index f7b33b0a4a..6fe847df50 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3917,6 +3917,10 @@ Try enclosing path to the editor with double quotes.    <!-- Spell check settings floater -->    <string name="UserDictionary">[User]</string> +  <!-- Experience Tools strings --> +  <string name="experience_tools_experience">Experience</string> + +    <!-- Conversation log messages -->    <string name="logging_calls_disabled_log_empty">      Conversations are not being logged. To begin keeping a log, choose "Save: Log only" or "Save: Log and transcripts" under Preferences > Chat. | 
