phoenix-firestorm/indra/newview/fslslbridge.cpp

1739 lines
55 KiB
C++

/**
* @file fslslbridge.cpp
* @brief FSLSLBridge implementation
*
* $LicenseInfo:firstyear=2011&license=fsviewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2011-2017, The Phoenix Firestorm Project, 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
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fscommon.h"
#include "fslslbridge.h"
#include "fslslbridgerequest.h"
#include "apr_base64.h" // For getScriptInfo()
#include "llagent.h"
#include "llappearancemgr.h"
#include "llattachmentsmgr.h"
#include "llavatarappearance.h"
#include "llinventoryfunctions.h"
#include "llmaniptranslate.h"
#include "llnotificationsutil.h"
#include "llpreviewscript.h"
#include "llselectmgr.h"
#include "llsdutil.h"
#include "llslurl.h"
#include "lltrans.h"
#include "llviewerassetupload.h"
#include "llviewercontrol.h"
#include "llviewerregion.h"
#if OPENSIM
#include "llviewernetwork.h"
#endif
static const std::string FS_BRIDGE_FOLDER = "#LSL Bridge";
static const std::string FS_BRIDGE_CONTAINER_FOLDER = "Landscaping";
static const U32 FS_BRIDGE_MAJOR_VERSION = 2;
static const U32 FS_BRIDGE_MINOR_VERSION = 29;
static const U32 FS_MAX_MINOR_VERSION = 99;
static const std::string UPLOAD_SCRIPT_CURRENT = "EBEDD1D2-A320-43f5-88CF-DD47BBCA5DFB.lsltxt";
static const std::string FS_STATE_ATTRIBUTE = "state=";
static const std::string FS_ERROR_ATTRIBUTE = "error=";
class NameCollectFunctor : public LLInventoryCollectFunctor
{
public:
NameCollectFunctor(std::string_view name)
{
sName = static_cast<std::string>(name);
}
virtual ~NameCollectFunctor() {}
virtual bool operator()(LLInventoryCategory* cat,
LLInventoryItem* item)
{
if (item)
{
return (item->getName() == sName);
}
return false;
}
private:
std::string sName;
};
//
//
// Bridge functionality
//
FSLSLBridge::FSLSLBridge():
mBridgeCreating(false),
mpBridge(nullptr),
mBridgeFolderID(LLUUID::null),
mIsFirstCallDone(false),
mAllowDetach(false),
mFinishCreation(false),
mTimerResult(FSLSLBridge::NO_TIMER)
{
LL_INFOS("FSLSLBridge") << "Constructing FSLSLBridge" << LL_ENDL;
mCurrentFullName = llformat("%s%d.%d", FS_BRIDGE_NAME.c_str(), FS_BRIDGE_MAJOR_VERSION, FS_BRIDGE_MINOR_VERSION);
gIdleCallbacks.addFunction(onIdle, this);
}
FSLSLBridge::~FSLSLBridge()
{
gIdleCallbacks.deleteFunction(onIdle, this);
}
void FSLSLBridge::onIdle(void* userdata)
{
FSLSLBridge* instance = static_cast<FSLSLBridge*>(userdata);
if (instance)
{
switch (instance->mTimerResult)
{
case START_CREATION_FINISHED:
instance->finishCleanUpPreCreation();
instance->setTimerResult(NO_TIMER);
break;
case CLEANUP_FINISHED:
instance->finishBridge();
instance->setTimerResult(NO_TIMER);
break;
case REATTACH_FINISHED:
{
LLViewerInventoryItem* inv_object = gInventory.getItem(instance->mReattachBridgeUUID);
if (inv_object && instance->getBridge() && instance->getBridge()->getUUID() == inv_object->getUUID())
{
LLAttachmentsMgr::instance().addAttachmentRequest(inv_object->getUUID(), FS_BRIDGE_POINT, TRUE, TRUE);
}
instance->mReattachBridgeUUID.setNull();
instance->setTimerResult(NO_TIMER);
}
break;
case SCRIPT_UPLOAD_FINISHED:
instance->checkBridgeScriptName();
instance->setTimerResult(NO_TIMER);
break;
case NO_TIMER:
default:
break;
}
}
}
void FSLSLBridge::setTimerResult(TimerResult result)
{
mTimerResult = result;
}
bool FSLSLBridge::lslToViewer(std::string_view message, const LLUUID& fromID, const LLUUID& ownerID)
{
LL_DEBUGS("FSLSLBridge") << message << LL_ENDL;
//<FS:TS> FIRE-962: Script controls for built-in AO
if (message.empty() || (message[0]) != '<')
{
return false; // quick exit if empty or no leading <
}
size_t closebracket = message.find('>');
size_t firstblank = message.find(' ');
size_t tagend;
if (closebracket == std::string::npos)
{
tagend = firstblank;
}
else if (firstblank == std::string::npos)
{
tagend = closebracket;
}
else
{
tagend = (closebracket < firstblank) ? closebracket : firstblank;
}
if (tagend == std::string::npos)
{
return false;
}
std::string_view tag = message.substr(0, tagend + 1);
std::string ourBridge = findFSCategory().asString();
//</FS:TS> FIRE-962
bool bridgeIsEnabled = gSavedSettings.getBOOL("UseLSLBridge");
bool status = false;
if (tag == "<bridgeURL>")
{
if (!bridgeIsEnabled)
{
// Hide handshake message if bridge is disabled in viewer settings but the bridge itself sent it, then quit - don't reply to the handshake
return true;
}
// brutish parsing
static const std::string bridge_url_tag = "<bridgeURL>";
static const std::string bridge_auth_tag = "<bridgeAuth>";
static const std::string bridge_ver_tag = "<bridgeVer>";
size_t urlStart = message.find(bridge_url_tag) + bridge_url_tag.size();
size_t urlEnd = message.find("</bridgeURL>");
size_t authStart = message.find(bridge_auth_tag) + bridge_auth_tag.size();
size_t authEnd = message.find("</bridgeAuth>");
size_t verStart = message.find(bridge_ver_tag) + bridge_ver_tag.size();
size_t verEnd = message.find("</bridgeVer>");
std::string bURL = static_cast<std::string>(message.substr(urlStart,urlEnd - urlStart));
std::string bAuth = static_cast<std::string>(message.substr(authStart,authEnd - authStart));
std::string bVer = static_cast<std::string>(message.substr(verStart,verEnd - verStart));
// Verify Authorization
if (ourBridge != bAuth)
{
LL_WARNS("FSLSLBridge") << "BridgeURL message received from ("<< bAuth <<") , but not from our registered bridge ("<< ourBridge <<"). Ignoring." << LL_ENDL;
// Failing bridge authorization automatically kicks off a bridge rebuild. This correctly handles the
// case where a user logs in from multiple computers which cannot have the bridgeAuth ID locally
// synchronized.
// If something that looks like our current bridge is attached but failed auth, detach and recreate.
const LLUUID catID = findFSCategory();
LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID);
if (fsBridge && get_is_item_worn(fsBridge->getUUID()))
{
mAllowDetach = true;
LLVOAvatarSelf::detachAttachmentIntoInventory(fsBridge->getUUID());
//This may have been an unfinished bridge. If so - stop the process before recreating.
if (getBridgeCreating())
{
setBridgeCreating(false);
}
recreateBridge();
return true;
}
// If something that didn't look like our current bridge failed auth, don't recreate, it might interfere with a bridge creation in progress
// or normal bridge startup. Bridge creation isn't threadsafe yet.
return true;
}
// Verify Version
std::string receivedBridgeVersion = llformat("%s%s", FS_BRIDGE_NAME.c_str(), bVer.c_str());
if (receivedBridgeVersion != mCurrentFullName)
{
LL_WARNS("FSLSLBridge") << "BridgeVer message received from ("<< bAuth <<") was ("<< receivedBridgeVersion <<"), but it should be different ("<< mCurrentFullName <<"). Recreating." << LL_ENDL;
recreateBridge();
return true;
}
// Save the inworld UUID of this attached bridge for later checking
mBridgeUUID = fromID;
// Get URL
mCurrentURL = bURL;
LL_INFOS("FSLSLBridge") << "New Bridge URL is: " << mCurrentURL << LL_ENDL;
if (!mpBridge)
{
LLUUID catID = findFSCategory();
LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID);
mpBridge = fsBridge;
}
status = viewerToLSL("URL Confirmed");
if (!mIsFirstCallDone)
{
//on first call from bridge, confirm that we are here
//then check options use
if (gSavedPerAccountSettings.getF32("UseLSLFlightAssist") > 0.f)
{
viewerToLSL(llformat("UseLSLFlightAssist|%.1f", gSavedPerAccountSettings.getF32("UseLSLFlightAssist")) );
LLNotificationsUtil::add("FlightAssistEnabled", LLSD());
}
// <FS:PP> Inform user, if movelock was enabled at login
if (gSavedPerAccountSettings.getBOOL("UseMoveLock"))
{
updateBoolSettingValue("UseMoveLock");
LLNotificationsUtil::add("MovelockEnabling", LLSD());
make_ui_sound("UISndMovelockToggle");
}
// </FS:PP>
updateBoolSettingValue("RelockMoveLockAfterMovement");
updateIntegrations();
mIsFirstCallDone = true;
}
// <FS:PP> FIRE-11924: Refresh movelock position after region change (crossing/teleporting), if lock was enabled
// Not called right after logging in, and only if movelock was enabled during transition
else if (gSavedPerAccountSettings.getBOOL("UseMoveLock"))
{
make_ui_sound("UISndMovelockToggle");
if (!gSavedSettings.getBOOL("RelockMoveLockAfterRegionChange"))
{
// Don't call for update here and only change setting to 'false', getCommitSignal()->connect->boost in llviewercontrol.cpp will send a message to Bridge anyway
gSavedPerAccountSettings.setBOOL("UseMoveLock", false);
LLNotificationsUtil::add("MovelockDisabling", LLSD());
}
else
{
// RelockMoveLockAfterRegionChange is 'true'? Then re-lock the movelock by sending a request to Bridge for coordinates update with current 'true' from UseMoveLock
updateBoolSettingValue("UseMoveLock");
}
}
// </FS:PP>
return true;
}
else if (tag == "<bridgeRequestError/>")
{
LL_WARNS("FSLSLBridge") << "Could not obtain URL for LSL bridge." << LL_ENDL;
return true;
}
//<FS:TS> FIRE-962: Script controls for built-in AO
if (fromID != mBridgeUUID || !bridgeIsEnabled)
{
return false; // ignore if not from the bridge, or bridge is disabled
}
if (tag == "<clientAO ")
{
// <FS:Zi> do nothing when the FS AO is disabled
if (!gSavedPerAccountSettings.getBOOL("UseAO"))
{
return true;
}
status = true;
size_t valuepos = message.find(FS_STATE_ATTRIBUTE);
if (valuepos != std::string::npos)
{
// <FS:Zi> send appropriate enable/disable messages to nearby chat - FIRE-24160
// use BOOL to satisfy windows compiler
BOOL aoWasPaused = gSavedPerAccountSettings.getBOOL("PauseAO");
BOOL aoStandsWasEnabled = gSavedPerAccountSettings.getBOOL("UseAOStands");
// </FS:Zi>
if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 2) == "on")
{
// <FS:Zi> Pause AO via bridge instead of switch AO on or off - FIRE-9305
gSavedPerAccountSettings.setBOOL("PauseAO", FALSE);
gSavedPerAccountSettings.setBOOL("UseAOStands", TRUE);
}
else if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 3) == "off")
{
// <FS:Zi> Pause AO via bridge instead of switch AO on or off - FIRE-9305
gSavedPerAccountSettings.setBOOL("PauseAO", TRUE);
}
else if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 7) == "standon")
{
gSavedPerAccountSettings.setBOOL("UseAOStands", TRUE);
}
else if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 8) == "standoff")
{
gSavedPerAccountSettings.setBOOL("UseAOStands", FALSE);
}
else
{
LL_WARNS("FSLSLBridge") << "AO control - Received unknown state" << LL_ENDL;
}
// <FS:Zi> send appropriate enable/disable messages to nearby chat - FIRE-24160
std::string aoMessage;
if (aoWasPaused != gSavedPerAccountSettings.getBOOL("PauseAO"))
{
if (aoWasPaused)
{
aoMessage = LLTrans::getString("FSAOResumedScript");
}
else
{
aoMessage = LLTrans::getString("FSAOPausedScript");
}
}
if (aoStandsWasEnabled != gSavedPerAccountSettings.getBOOL("UseAOStands"))
{
if (aoStandsWasEnabled)
{
aoMessage = LLTrans::getString("FSAOStandsPausedScript");
}
else
{
aoMessage = LLTrans::getString("FSAOStandsResumedScript");
}
}
if (!aoMessage.empty())
{
LLSD args;
args["AO_MESSAGE"] = aoMessage;
LLNotificationsUtil::add("FSAOScriptedNotification", args);
}
// </FS:Zi>
}
}
//</FS:TS> FIRE-962
// <FS:PP> Get script info response
else if (tag == "<bridgeGetScriptInfo>")
{
size_t tag_size = tag.size();
status = true;
size_t getScriptInfoEnd = message.find("</bridgeGetScriptInfo>");
if (getScriptInfoEnd != std::string::npos)
{
std::string getScriptInfoString = static_cast<std::string>(message.substr(tag_size, getScriptInfoEnd - tag_size));
std::istringstream strStreamGetScriptInfo(getScriptInfoString);
std::string scriptInfoToken;
LLSD scriptInfoArray = LLSD::emptyArray();
S32 scriptInfoArrayCount = 0;
while (std::getline(strStreamGetScriptInfo, scriptInfoToken, ','))
{
LLStringUtil::trim(scriptInfoToken);
if (scriptInfoArrayCount == 0 || scriptInfoArrayCount == 6 || scriptInfoArrayCount == 11 || scriptInfoArrayCount == 12 || scriptInfoArrayCount == 13 || scriptInfoArrayCount == 14 || scriptInfoArrayCount == 25)
{
// First value, OBJECT_NAME, should be passed from Bridge as encoded in base64
// Encoding eliminates problems with special characters and commas for CSV
S32 base64Length = apr_base64_decode_len(scriptInfoToken.c_str());
if (base64Length)
{
std::vector<U8> base64VectorData;
base64VectorData.resize(base64Length);
base64Length = apr_base64_decode_binary(&base64VectorData[0], scriptInfoToken.c_str());
base64VectorData.resize(base64Length);
std::string base64FinalData(base64VectorData.begin(), base64VectorData.end());
scriptInfoToken = base64FinalData;
}
else
{
LL_WARNS("FSLSLBridge") << "ScriptInfo - value with index " << scriptInfoArrayCount << " cannot be decoded" << LL_ENDL;
}
}
scriptInfoArray.append(scriptInfoToken);
++scriptInfoArrayCount;
}
if (scriptInfoArrayCount == 6 || scriptInfoArrayCount == 27)
{
LLStringUtil::format_map_t args;
args["OBJECT_NAME"] = scriptInfoArray[0].asString();
args["OBJECT_RUNNING_SCRIPT_COUNT"] = scriptInfoArray[1].asString();
args["OBJECT_TOTAL_SCRIPT_COUNT"] = scriptInfoArray[2].asString();
args["OBJECT_SCRIPT_MEMORY"] = scriptInfoArray[3].asString();
args["OBJECT_SCRIPT_TIME"] = scriptInfoArray[4].asString();
if (scriptInfoArray[5].asString() != "0.000000")
{
LLStringUtil::format_map_t args2;
args2["OBJECT_CHARACTER_TIME"] = scriptInfoArray[5].asString();
args["PATHFINDING_TEXT"] = " " + format_string(LLTrans::getString("fsbridge_script_info_pf"), args2);
}
else
{
args["PATHFINDING_TEXT"] = "";
}
report_to_nearby_chat(format_string(LLTrans::getString("fsbridge_script_info"), args));
if (scriptInfoArrayCount == 27)
{
LLStringUtil::format_map_t args3;
args3["OBJECT_DESC"] = scriptInfoArray[6].asString();
args3["OBJECT_ROOT"] = scriptInfoArray[7].asString();
args3["OBJECT_PRIM_COUNT"] = scriptInfoArray[8].asString();
args3["OBJECT_PRIM_EQUIVALENCE"] = scriptInfoArray[9].asString();
args3["OBJECT_TOTAL_INVENTORY_COUNT"] = scriptInfoArray[10].asString();
args3["OBJECT_VELOCITY"] = scriptInfoArray[11].asString();
args3["OBJECT_POS"] = scriptInfoArray[12].asString();
args3["OBJECT_ROT"] = scriptInfoArray[13].asString();
args3["OBJECT_OMEGA"] = scriptInfoArray[14].asString();
args3["OBJECT_CREATOR"] = LLSLURL("agent", scriptInfoArray[15].asUUID(), "inspect").getSLURLString();
args3["OBJECT_OWNER"] = scriptInfoArray[16].asUUID().isNull() ? LLTrans::getString("GroupOwned") : LLSLURL("agent", scriptInfoArray[16].asUUID(), "inspect").getSLURLString();
args3["OBJECT_LAST_OWNER_ID"] = scriptInfoArray[17].asUUID().notNull() ? LLSLURL("agent", scriptInfoArray[17].asUUID(), "inspect").getSLURLString() : "---";
args3["OBJECT_REZZER_KEY"] = scriptInfoArray[18].asString();
args3["OBJECT_GROUP"] = scriptInfoArray[19].asUUID().notNull() ? LLSLURL("group", scriptInfoArray[19].asUUID(), "inspect").getSLURLString() : "---";
args3["OBJECT_CREATION_TIME"] = scriptInfoArray[20].asString();
args3["OBJECT_REZ_TIME"] = scriptInfoArray[21].asString();
args3["OBJECT_PATHFINDING_TYPE"] = scriptInfoArray[22].asString();
args3["OBJECT_ATTACHED_POINT"] = (scriptInfoArray[23].asInteger() < 1 || scriptInfoArray[23].asInteger() > 255) ? "---" : LLTrans::getString(LLAvatarAppearance::getAttachmentPointName(scriptInfoArray[23].asInteger()));
args3["OBJECT_TEMP_ATTACHED"] = scriptInfoArray[24].asInteger() == 1 ? LLTrans::getString("Yes") : LLTrans::getString("No");
args3["AVATAR_POS"] = scriptInfoArray[25].asString();
args3["INSPECTING_KEY"] = scriptInfoArray[26].asString();
report_to_nearby_chat(format_string(LLTrans::getString("fsbridge_script_info_ext"), args3));
}
}
else
{
report_to_nearby_chat(LLTrans::getString("fsbridge_error_scriptinfonotfound"));
LL_WARNS("FSLSLBridge") << "ScriptInfo - Object to check is invalid or out of range (warning returned by viewer, data somehow passed bridge script check)" << LL_ENDL;
}
}
else
{
report_to_nearby_chat(LLTrans::getString("fsbridge_error_scriptinfomalformed"));
LL_WARNS("FSLSLBridge") << "ScriptInfo - Received malformed response from bridge (missing ending tag)" << LL_ENDL;
}
}
// </FS:PP>
// <FS:PP> Movelock state response
else if (tag == "<bridgeMovelock ")
{
status = true;
size_t valuepos = message.find(FS_STATE_ATTRIBUTE);
if (valuepos != std::string::npos)
{
if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 1) == "1")
{
LLNotificationsUtil::add("MovelockEnabled", LLSD());
}
else if (message.substr(valuepos + FS_STATE_ATTRIBUTE.size(), 1) == "0")
{
LLNotificationsUtil::add("MovelockDisabled", LLSD());
}
else
{
LL_WARNS("FSLSLBridge") << "Movelock - Received unknown state" << LL_ENDL;
}
}
}
// </FS:PP>
// <FS:PP> Error responses handling
else if (tag == "<bridgeError ")
{
status = true;
size_t valuepos = message.find(FS_ERROR_ATTRIBUTE);
if (valuepos != std::string::npos)
{
if (message.substr(valuepos + FS_ERROR_ATTRIBUTE.size(), 9) == "injection")
{
report_to_nearby_chat(LLTrans::getString("fsbridge_error_injection"));
LL_WARNS("FSLSLBridge") << "Script injection detected" << LL_ENDL;
}
else if (message.substr(valuepos + FS_ERROR_ATTRIBUTE.size(), 18) == "scriptinfonotfound")
{
report_to_nearby_chat(LLTrans::getString("fsbridge_error_scriptinfonotfound"));
LL_WARNS("FSLSLBridge") << "ScriptInfo - Object to check is invalid or out of range (warning returned by bridge)" << LL_ENDL;
}
else if (message.substr(valuepos + FS_ERROR_ATTRIBUTE.size(), 7) == "wrongvm")
{
report_to_nearby_chat(LLTrans::getString("fsbridge_error_wrongvm"));
LL_WARNS("FSLSLBridge") << "Script is using old LSO (16 KB memory limit) instead of new Mono (64 KB memory limit) virtual machine, which creates high probability of stack-heap collision and bridge failure by running out of memory" << LL_ENDL;
}
else
{
LL_WARNS("FSLSLBridge") << "ErrorReporting - Received unknown error type" << LL_ENDL;
}
}
}
// </FS:PP>
return status;
}
bool FSLSLBridge::canUseBridge()
{
static LLCachedControl<bool> sUseLSLBridge(gSavedSettings, "UseLSLBridge");
return (isBridgeValid() && sUseLSLBridge && !mCurrentURL.empty());
}
bool FSLSLBridge::viewerToLSL(std::string_view message, Callback_t aCallback)
{
LL_DEBUGS("FSLSLBridge") << message << LL_ENDL;
if (!canUseBridge())
{
return false;
}
Callback_t pCallback = aCallback;
if (!pCallback)
{
pCallback = FSLSLBridgeRequest_Success;
}
// Calling data() should be fine here since message is a view on a null-terminated string
LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(mCurrentURL, LLSD(message.data()), pCallback, FSLSLBridgeRequest_Failure);
return true;
}
bool FSLSLBridge::updateBoolSettingValue(const std::string& msgVal)
{
const std::string boolVal = gSavedPerAccountSettings.getBOOL(msgVal) ? "1" : "0";
return viewerToLSL(msgVal + "|" + boolVal);
}
bool FSLSLBridge::updateBoolSettingValue(const std::string& msgVal, bool contentVal)
{
const std::string boolVal = contentVal ? "1" : "0";
return viewerToLSL(msgVal + "|" + boolVal);
}
void FSLSLBridge::updateIntegrations()
{
viewerToLSL(llformat(
"ExternalIntegration|%d|%d",
gSavedPerAccountSettings.getBOOL("BridgeIntegrationOC"),
gSavedPerAccountSettings.getBOOL("BridgeIntegrationLM")
) );
}
//
//Bridge initialization
//
void FSLSLBridge::recreateBridge()
{
LL_INFOS("FSLSLBridge") << "Recreating bridge start" << LL_ENDL;
// Don't create on OpenSim. We need to fallback to another creation process there, unfortunately.
// There is no way to ensure a rock object will ever be in a grid's Library.
#if OPENSIM
if (LLGridManager::getInstance()->isInOpenSim())
{
return;
}
#endif
if (!gSavedSettings.getBOOL("UseLSLBridge"))
{
//<FS:TS> FIRE-11746: Recreate should throw error if disabled
LL_WARNS("FSLSLBridge") << "Asked to create bridge, but bridge is disabled. Aborting." << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_cant_create_disabled"));
setBridgeCreating(false);
//</FS:TS> FIRE-11746
return;
}
if (gSavedSettings.getBOOL("NoInventoryLibrary"))
{
LL_WARNS("FSLSLBridge") << "Asked to create bridge, but we don't have a library. Aborting." << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_no_library"));
setBridgeCreating(false);
return;
}
if (mBridgeCreating)
{
LL_WARNS("FSLSLBridge") << "Bridge creation already in progress, aborting new attempt." << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_already_creating"));
return;
}
//announce yourself
report_to_nearby_chat(LLTrans::getString("fsbridge_creating"));
setupFSCategory([](const LLUUID& bridge_folder_id)
{
FSLSLBridgeInventoryPreCreationCleanupObserver* bridgeInventoryObserver = new FSLSLBridgeInventoryPreCreationCleanupObserver(bridge_folder_id);
bridgeInventoryObserver->startFetch();
if (bridgeInventoryObserver->isFinished())
{
bridgeInventoryObserver->done();
}
else
{
gInventory.addObserver(bridgeInventoryObserver);
}
});
}
void FSLSLBridge::cleanUpPreCreation()
{
LL_INFOS("FSLSLBridge") << "Starting clean up prior creation - bridge category has been loaded" << LL_ENDL;
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
NameCollectFunctor namefunctor(mCurrentFullName);
gInventory.collectDescendentsIf(getBridgeFolder(), cats, items, FALSE, namefunctor);
mAllowedDetachables.clear();
for (const auto& item : items)
{
const LLUUID& item_id = item->getUUID();
if (get_is_item_worn(item_id))
{
LL_INFOS("FSLSLBridge") << "Found worn object " << item_id << " bridge category - detaching..." << LL_ENDL;
mAllowedDetachables.push_back(item_id);
LLVOAvatarSelf::detachAttachmentIntoInventory(item_id);
}
}
// Immediately start with finishCleanUpPreCreation if we don't have
// any attachments we need to wait for until they got detached
if (mAllowedDetachables.size() == 0)
{
LL_INFOS("FSLSLBridge") << "Not waiting for any objects to get detached. Starting pre-creation cleanup immediately" << LL_ENDL;
finishCleanUpPreCreation();
}
else
{
LL_INFOS("FSLSLBridge") << "Waiting for any objects to get detached. Pre-creation cleanup will start after objects got detached" << LL_ENDL;
}
}
// Called either by cleanUpPreCreation directly or via FSLSLBridgeStartCreationTimer
// after all pending detachments have been processed
void FSLSLBridge::finishCleanUpPreCreation()
{
LL_INFOS("FSLSLBridge") << "Finishing cleanup prior creation" << LL_ENDL;
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
NameCollectFunctor namefunctor(mCurrentFullName);
gInventory.collectDescendentsIf(getBridgeFolder(), cats, items, FALSE, namefunctor);
for (const auto& item : items)
{
LL_INFOS("FSLSLBridge") << "Bridge folder cleanup: Deleting " << item->getName() << " (" << item->getUUID() << ")" << LL_ENDL;
remove_inventory_item(item->getUUID(), nullptr, true); // Don't wait for callback from server to update inventory model
}
gInventory.notifyObservers();
// clear the stored bridge ID - we are starting over.
mpBridge = nullptr; //the object itself will get cleaned up when new one is created.
mCurrentURL.clear();
setBridgeCreating(true);
mFinishCreation = false;
createNewBridge();
}
// Called by RlvAttachmentLockWatchdog::onDetach to check if attachment
// is allowed to get detached - required if attachment spot is locked!
bool FSLSLBridge::canDetach(const LLUUID& item_id)
{
LL_DEBUGS("FSLSLBridge") << "canDetach" << LL_ENDL;
return ((mpBridge && item_id == mpBridge->getUUID()) || (std::find(mAllowedDetachables.begin(), mAllowedDetachables.end(), item_id) != mAllowedDetachables.end()));
}
void FSLSLBridge::initBridge()
{
LL_INFOS("FSLSLBridge") << "Initializing FSLSLBridge" << LL_ENDL;
if (!gSavedSettings.getBOOL("UseLSLBridge"))
{
return;
}
if (gSavedSettings.getBOOL("NoInventoryLibrary"))
{
LL_WARNS("FSLSLBridge") << "Asked to create bridge, but we don't have a library. Aborting." << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_no_library"));
setBridgeCreating(false);
return;
}
setupFSCategory([this](const LLUUID& bridge_folder_id)
{
LLUUID libCatID = findFSBridgeContainerCategory();
//check for inventory load
// AH: Use overloaded LLInventoryFetchDescendentsObserver to check for load of
// bridge and bridge rock category before doing anything!
LL_INFOS("FSLSLBridge") << "initBridge called. gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL;
uuid_vec_t cats{ bridge_folder_id, libCatID };
FSLSLBridgeInventoryObserver* bridgeInventoryObserver = new FSLSLBridgeInventoryObserver(cats);
bridgeInventoryObserver->startFetch();
if (bridgeInventoryObserver->isFinished())
{
bridgeInventoryObserver->done();
}
else
{
gInventory.addObserver(bridgeInventoryObserver);
}
});
}
// Gets called by the Init, when inventory loaded (FSLSLBridgeInventoryObserver::done()).
void FSLSLBridge::startCreation()
{
LL_INFOS("FSLSLBridge") << "Entering startCreation" << LL_ENDL;
if (getBridgeCreating())
{
LL_INFOS("FSLSLBridge") << "startCreation called while recreating bridge - aborting" << LL_ENDL;
return;
}
if (!isAgentAvatarValid())
{
LL_WARNS("FSLSLBridge") << "AgentAvatar is not valid" << LL_ENDL;
return;
}
LL_INFOS("FSLSLBridge") << "startCreation called. gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL;
//if bridge object doesn't exist - create and attach it, update script.
setupFSCategory([this](const LLUUID& bridge_folder_id)
{
LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, bridge_folder_id);
//detach everything else
LL_INFOS("FSLSLBridge") << "Detaching other bridges..." << LL_ENDL;
detachOtherBridges();
if (!fsBridge)
{
LL_INFOS("FSLSLBridge") << "Bridge not found in inventory, creating new one..." << LL_ENDL;
// Don't create on OpenSim. We need to fallback to another creation process there, unfortunately.
// There is no way to ensure a rock object will ever be in a grid's Library.
#if OPENSIM
if (LLGridManager::getInstance()->isInOpenSim())
{
return;
}
#endif
setBridgeCreating(true);
mFinishCreation = false;
//announce yourself
report_to_nearby_chat(LLTrans::getString("fsbridge_creating"));
createNewBridge();
}
else
{
//TODO need versioning - see isOldBridgeVersion()
mpBridge = fsBridge;
if (!isItemAttached(mpBridge->getUUID()))
{
if (!LLAppearanceMgr::instance().getIsInCOF(mpBridge->getUUID()))
{
LL_INFOS("FSLSLBridge") << "Bridge not attached but found in inventory, reattaching..." << LL_ENDL;
//Is this a valid bridge - wear it.
LLAttachmentsMgr::instance().addAttachmentRequest(mpBridge->getUUID(), FS_BRIDGE_POINT, TRUE, TRUE);
//from here, the attach should report to ProcessAttach and make sure bridge is valid.
}
else
{
LL_INFOS("FSLSLBridge") << "Bridge not found but in CoF. Waiting for automatic attach..." << LL_ENDL;
}
}
}
});
}
void FSLSLBridge::createNewBridge()
{
//check if user has a bridge
const LLUUID catID = getBridgeFolder();
//attach the Linden rock from the library (will resize as soon as attached)
const LLUUID libID = gInventory.getLibraryRootFolderID();
LLViewerInventoryItem* libRock = findInvObject(LIB_ROCK_NAME, libID);
//shouldn't happen but just in case
if (libRock)
{
LL_INFOS("FSLSLBridge") << "Cloning a new Bridge container from the Library..." << LL_ENDL;
//copy the library item to inventory and put it on
LLPointer<LLInventoryCallback> cb = new FSLSLBridgeRezCallback();
copy_inventory_item(gAgent.getID(), libRock->getPermissions().getOwner(), libRock->getUUID(), catID, mCurrentFullName, cb);
}
else
{
LL_WARNS("FSLSLBridge") << "Bridge container not found in the Library!" << LL_ENDL;
// AH: Set to false or we won't be able to start another bridge creation
// process in this session!
setBridgeCreating(false);
}
}
void FSLSLBridge::processAttach(LLViewerObject* object, const LLViewerJointAttachment* attachment)
{
LL_INFOS("FSLSLBridge") << "Entering processAttach, checking the bridge container - gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL;
if ((!gAgentAvatarp->isSelf()) || (attachment->getName() != FS_BRIDGE_ATTACHMENT_POINT_NAME))
{
LL_WARNS("FSLSLBridge") << "Bridge not created. Our bridge container attachment isn't named correctly." << LL_ENDL;
if (mBridgeCreating)
{
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_creation_bad_name"));
setBridgeCreating(false); //in case we interrupted the creation
}
return;
}
LLViewerInventoryItem* fsObject = gInventory.getItem(object->getAttachmentItemID());
if (!fsObject) //just in case
{
LL_WARNS("FSLSLBridge") << "Bridge container is still NULL in inventory. Aborting." << LL_ENDL;
if (mBridgeCreating)
{
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_creation_null"));
setBridgeCreating(false); //in case we interrupted the creation
}
return;
}
if (!mpBridge) //user is attaching an existing bridge?
{
LL_INFOS("FSLSLBridge") << "mpBridge is NULL" << LL_ENDL;
//is it the right version?
if (fsObject->getName() != mCurrentFullName)
{
mAllowDetach = true;
LLVOAvatarSelf::detachAttachmentIntoInventory(fsObject->getUUID());
LL_WARNS("FSLSLBridge") << "Attempt to attach to bridge point an object other than current bridge" << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_attach_wrong_object"));
if (mBridgeCreating)
{
setBridgeCreating(false); //in case we interrupted the creation
}
return;
}
//is it in the right place?
const LLUUID catID = findFSCategory();
if (catID != fsObject->getParentUUID())
{
//the object is not where we think it is. Kick it off.
mAllowDetach = true;
LLVOAvatarSelf::detachAttachmentIntoInventory(fsObject->getUUID());
LL_WARNS("FSLSLBridge") << "Bridge container isn't in the correct inventory location. Detaching it and aborting." << LL_ENDL;
if (mBridgeCreating)
{
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_attach_wrong_location"));
setBridgeCreating(false); //in case we interrupted the creation
}
return;
}
mpBridge = fsObject;
}
LL_INFOS("FSLSLBridge") << "Bridge container is attached, mpBridge not NULL, avatar is self, point is bridge, all is good." << LL_ENDL;
if (fsObject->getUUID() != mpBridge->getUUID())
{
//something odd just got attached to bridge?
LL_WARNS("FSLSLBridge") << "Something unknown just got attached to bridge point, detaching and aborting." << LL_ENDL;
if (mBridgeCreating)
{
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_attach_point_in_use"));
setBridgeCreating(false); //in case we interrupted the creation
}
LLVOAvatarSelf::detachAttachmentIntoInventory(mpBridge->getUUID());
return;
}
LL_DEBUGS("FSLSLBridge") << "Bridge container found is the same bridge we saved, id matched." << LL_ENDL;
if (!mBridgeCreating) //just an attach. See what it is
{
// AH: We need to request objects inventory first before we can
// do anything with it!
LL_INFOS("FSLSLBridge") << "Requesting bridge inventory contents..." << LL_ENDL;
object->registerInventoryListener(this, nullptr);
object->requestInventory();
}
else
{
configureBridgePrim(object);
}
}
void FSLSLBridge::inventoryChanged(LLViewerObject* object,
LLInventoryObject::object_list_t* inventory,
S32 serial_num,
void* user_data)
{
object->removeInventoryListener(this);
LL_INFOS("FSLSLBridge") << "Received object inventory for existing bridge prim. Checking contents..." << LL_ENDL;
//are we attaching the right thing? Check size and script
LLInventoryObject::object_list_t inventory_objects;
object->getInventoryContents(inventory_objects);
if (object->flagInventoryEmpty())
{
LL_INFOS("FSLSLBridge") << "Empty bridge detected- re-enter creation process" << LL_ENDL;
setBridgeCreating(true);
}
else if (inventory_objects.size() > 0)
{
S32 count(0);
LLInventoryObject::object_list_t::iterator it = inventory_objects.begin();
LLInventoryObject::object_list_t::iterator end = inventory_objects.end();
bool isOurScript = false;
for ( ; it != end; ++it)
{
LLInventoryItem* item = ((LLInventoryItem*)((LLInventoryObject*)(*it)));
// AH: Somehow always contains a wonky object item with creator
// UUID = NULL UUID and asset type AT_NONE - don't count it
if (item->getType() != LLAssetType::AT_NONE)
{
count++;
}
if (item->getType() == LLAssetType::AT_LSL_TEXT)
{
if (item->getCreatorUUID() == gAgentID)
{
isOurScript = true;
}
else //problem, not our script
{
LL_WARNS("FSLSLBridge") << "The bridge inventory contains a script not created by user." << LL_ENDL;
}
}
}
if (count == 1 && isOurScript) //We attached a valid bridge. Run along.
{
LL_INFOS("FSLSLBridge") << "Bridge inventory is okay." << LL_ENDL;
}
else
{
report_to_nearby_chat(LLTrans::getString("fsbridge_warning_unexpected_items"));
LL_WARNS("FSLSLBridge") << "The bridge inventory contains items other than bridge script." << LL_ENDL;
if (!isOurScript) //some junk but no valid script? Unlikely to happen, but lets add script anyway.
{
setBridgeCreating(true);
}
}
}
else
{
LL_WARNS("FSLSLBridge") << "Bridge not empty, but we're unable to retrieve contents." << LL_ENDL;
}
//modify the rock size and texture
if (getBridgeCreating())
{
mFinishCreation = false;
configureBridgePrim(object);
}
else if (mFinishCreation)
{
LL_INFOS("FSLSLBridge") << "Bridge created." << LL_ENDL;
mFinishCreation = false;
mAllowedDetachables.clear();
report_to_nearby_chat(LLTrans::getString("fsbridge_created"));
}
}
void FSLSLBridge::configureBridgePrim(LLViewerObject* object)
{
if (!isBridgeValid())
{
LL_WARNS("FSLSLBridge") << "Bridge not valid" << LL_ENDL;
return;
}
//modify the rock size and texture
LL_INFOS("FSLSLBridge") << "Bridge container found after attachment, configuring prim..." << LL_ENDL;
setupBridgePrim(object);
//add bridge script to object
LL_INFOS("FSLSLBridge") << "Creating bridge script..." << LL_ENDL;
create_script_inner();
}
void FSLSLBridge::processDetach(LLViewerObject* object, const LLViewerJointAttachment* attachment)
{
LL_INFOS("FSLSLBridge") << "Entering processDetach" << LL_ENDL;
// Begin cleanup part
uuid_vec_t::iterator found = std::find(mAllowedDetachables.begin(), mAllowedDetachables.end(), object->getAttachmentItemID());
if (found != mAllowedDetachables.end())
{
mAllowedDetachables.erase(found);
if (mAllowedDetachables.size() == 0)
{
LL_INFOS("FSLSLBridge") << "No attached objects left. Starting creation timer..." << LL_ENDL;
// Wait a few seconds before calling finishCleanUpPreCreation
// which will clean up the LSLBridge folder
new FSLSLBridgeStartCreationTimer();
}
return;
}
// End cleanup part
if (gSavedSettings.getBOOL("UseLSLBridge") && !mAllowDetach)
{
LL_INFOS("FSLSLBridge") << "Re-attaching bridge" << LL_ENDL;
LLViewerInventoryItem* inv_object = gInventory.getItem(object->getAttachmentItemID());
if (inv_object && FSLSLBridge::instance().mpBridge && FSLSLBridge::instance().mpBridge->getUUID() == inv_object->getUUID())
{
LLAttachmentsMgr::instance().addAttachmentRequest(inv_object->getUUID(), FS_BRIDGE_POINT, TRUE, TRUE);
}
return;
}
mAllowDetach = false;
if (gAgentAvatarp.isNull() || (!gAgentAvatarp->isSelf()) || (!attachment) || (attachment->getName() != FS_BRIDGE_ATTACHMENT_POINT_NAME))
{
LL_WARNS("FSLSLBridge") << "Couldn't detach bridge, object has wrong name or avatar wasn't self." << LL_ENDL;
return;
}
LLViewerInventoryItem* fsObject = gInventory.getItem(object->getAttachmentItemID());
if (!fsObject) //just in case
{
LL_WARNS("FSLSLBridge") << "Couldn't detach bridge. inventory object was NULL." << LL_ENDL;
return;
}
if (mFinishCreation)
{
LL_INFOS("FSLSLBridge") << "Bridge detached to save settings. Starting re-attach timer..." << LL_ENDL;
mReattachBridgeUUID = object->getAttachmentItemID();
new FSLSLBridgeReAttachTimer();
return;
}
//is it in the right place?
const LLUUID catID = findFSCategory();
if (catID != fsObject->getParentUUID())
{
//that was in the wrong place. It's not ours.
LL_WARNS("FSLSLBridge") << "Bridge seems to be the wrong inventory category. Aborting detachment." << LL_ENDL;
return;
}
if (mpBridge && mpBridge->getUUID() == fsObject->getUUID())
{
mpBridge = nullptr;
report_to_nearby_chat(LLTrans::getString("fsbridge_detached"));
mIsFirstCallDone = false;
if (mBridgeCreating)
{
report_to_nearby_chat(LLTrans::getString("fsbridge_warning_not_finished"));
setBridgeCreating(false); //in case we interrupted the creation
mAllowedDetachables.clear();
}
}
LL_INFOS("FSLSLBridge") << "processDetach Finished" << LL_ENDL;
}
void FSLSLBridge::setupBridgePrim(LLViewerObject* object)
{
LL_DEBUGS("FSLSLBridge") << "Entering bridge container setup..." << LL_ENDL;
if (!object->getRegion())
return;
LLProfileParams profParams(LL_PCODE_PROFILE_CIRCLE, 0.230f, 0.250f, 0.95f);
LLPathParams pathParams(LL_PCODE_PATH_CIRCLE, 0.2f, 0.22f,
0.f, 350.f, //scale
0.f, 0.f, //shear
0.f, 0.f, //twist
0.f, //offset
0.f, 0.f, //taper
0.05f, 0.05f); //revolutions, skew
pathParams.setRevolutions(1.f);
object->setVolume(LLVolumeParams(profParams, pathParams), 0);
object->sendShapeUpdate();
// Set scale and position
object->setScale(LLVector3(0.01f, 0.01f, 0.01f));
object->setPosition(LLVector3(2.f, 2.f, 2.f));
LLManip::rebuild(object);
gAgentAvatarp->clampAttachmentPositions();
U8 data[24];
htolememcpy(&data[0], &(object->getPosition().mV), MVT_LLVector3, 12);
htolememcpy(&data[12], &(object->getScale().mV), MVT_LLVector3, 12);
gMessageSystem->newMessage("MultipleObjectUpdate");
gMessageSystem->nextBlockFast(_PREHASH_AgentData);
gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID);
gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID);
gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID());
gMessageSystem->addU8Fast(_PREHASH_Type, UPD_POSITION | UPD_SCALE | UPD_LINKED_SETS);
gMessageSystem->addBinaryDataFast(_PREHASH_Data, data, 24);
gMessageSystem->sendReliable(object->getRegion()->getHost());
// Set textures
for (U8 i = 0; i < object->getNumTEs(); ++i)
{
LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture( IMG_INVISIBLE );
object->setTEImage(i, image); //transparent texture
}
object->sendTEUpdate();
object->addFlags(FLAGS_TEMPORARY_ON_REZ);
object->updateFlags();
object->setChanged(LLXform::MOVED | LLXform::SILHOUETTE | LLXform::TEXTURE);
object->markForUpdate();
LL_DEBUGS("FSLSLBridge") << "End bridge container setup." << LL_ENDL;
}
void FSLSLBridge::create_script_inner()
{
if (!isBridgeValid())
{
LL_WARNS("FSLSLBridge") << "Bridge no valid" << LL_ENDL;
return;
}
const LLUUID catID = getBridgeFolder();
LLPointer<LLInventoryCallback> cb = new FSLSLBridgeScriptCallback();
create_inventory_item(gAgentID,
gAgentSessionID,
catID,
LLTransactionID::tnull,
mCurrentFullName,
mCurrentFullName,
LLAssetType::AT_LSL_TEXT,
LLInventoryType::IT_LSL,
NO_INV_SUBTYPE,
PERM_ALL,
cb);
}
//
// Bridge rez callback
//
void FSLSLBridgeRezCallback::fire(const LLUUID& inv_item)
{
// this is the first attach - librock got copied and worn on hand - but the ID is now real.
if ((FSLSLBridge::instance().getBridge() != NULL))
{
LL_INFOS("FSLSLBridge") << "Ignoring bridgerezcallback, already have bridge" << LL_ENDL;
return;
}
if (inv_item.isNull())
{
LL_INFOS("FSLSLBridge") << "Ignoring bridgerezcallback, new bridge is null" << LL_ENDL;
return;
}
if (!FSLSLBridge::instance().getBridgeCreating())
{
LL_INFOS("FSLSLBridge") << "Ignoring bridgerezcallback, we're not flagged as creating a bridge." << LL_ENDL;
return;
}
if (!isAgentAvatarValid())
{
LL_WARNS("FSLSLBridge") << "Agent is 0, bailing out" << LL_ENDL;
return;
}
LL_INFOS("FSLSLBridge") << "Bridge rez callback fired, looking for object..." << LL_ENDL;
LLViewerInventoryItem* item = gInventory.getItem(inv_item);
item->setDescription(item->getName());
item->setComplete(TRUE);
item->updateServer(FALSE);
gInventory.updateItem(item);
gInventory.notifyObservers();
//from this point on, this is our bridge - accept no substitutes!
FSLSLBridge::instance().setBridge(item);
LL_INFOS("FSLSLBridge") << "Attaching bridge to the 'bridge' attachment point..." << LL_ENDL;
LLAttachmentsMgr::instance().addAttachmentRequest(inv_item, FS_BRIDGE_POINT, TRUE, TRUE);
}
//
// Bridge script creation callback
//
void FSLSLBridgeScriptCallback::fire(const LLUUID& inv_item)
{
if (inv_item.isNull() || !FSLSLBridge::instance().getBridgeCreating())
{
LL_WARNS("FSLSLBridge") << "BridgeScriptCallback fired, but target item was null or bridge isn't marked as under creation. Ignoring." << LL_ENDL;
return;
}
LLViewerInventoryItem* item = gInventory.getItem(inv_item);
if (!item)
{
LL_WARNS("FSLSLBridge") << "BridgeScriptCallback Can't find target item in inventory. Ignoring." << LL_ENDL;
return;
}
if (!isAgentAvatarValid())
{
LL_WARNS("FSLSLBridge") << "Agent is 0, bailing out" << LL_ENDL;
return;
}
gInventory.updateItem(item);
gInventory.notifyObservers();
LLViewerObject* obj(nullptr);
if (FSLSLBridge::instance().isBridgeValid())
{
obj = gAgentAvatarp->getWornAttachment(FSLSLBridge::instance().getBridge()->getUUID());
}
else
{
LL_WARNS("FSLSLBridge") << "Bridge non valid" << LL_ENDL;
}
//caps import
std::string url = gAgent.getRegionCapability("UpdateScriptAgent");
bool cleanup = false;
if (!url.empty() && obj)
{
std::string buffer;
const std::string fName = prepUploadFile(buffer);
if (!fName.empty())
{
LL_INFOS("FSLSLBridge") << "Updating script ID for bridge and enqueing upload. Inventory ID: " << inv_item.asString() << LL_ENDL;
FSLSLBridge::instance().mScriptItemID = inv_item;
LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(obj->getID(), inv_item, LLScriptAssetUpload::MONO, true, LLUUID::null, buffer,
[](LLUUID, LLUUID, LLUUID, LLSD) {
FSLSLBridge::getInstance()->setTimerResult(FSLSLBridge::SCRIPT_UPLOAD_FINISHED);
}, nullptr));
LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
}
else
{
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_creation_create_script"));
cleanup = true;
}
}
else
{
cleanup = true;
}
if (cleanup)
{
//can't complete bridge creation - detach and remove object, remove script
//try to clean up and go away. Fail.
if (FSLSLBridge::instance().isBridgeValid())
{
FSLSLBridge::instance().mAllowDetach = true;
LLVOAvatarSelf::detachAttachmentIntoInventory(FSLSLBridge::instance().getBridge()->getUUID());
}
FSLSLBridge::instance().cleanUpBridge();
//also clean up script remains
remove_inventory_item(item->getUUID(), nullptr, true);
gInventory.notifyObservers();
LL_WARNS("FSLSLBridge") << "Can't update bridge script. Purging bridge." << LL_ENDL;
return;
}
}
std::string FSLSLBridgeScriptCallback::prepUploadFile(std::string &aBuffer)
{
const std::string fName = gDirUtilp->getExpandedFilename(LL_PATH_FS_RESOURCES, UPLOAD_SCRIPT_CURRENT);
const std::string fNew = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,UPLOAD_SCRIPT_CURRENT);
LLFILE* fpIn = LLFile::fopen(fName, "rt");
if (!fpIn)
{
LL_WARNS("FSLSLBridge") << "Cannot open script resource file" << LL_ENDL;
return std::string();
}
fseek(fpIn, 0, SEEK_END);
long lSize = ftell(fpIn);
rewind(fpIn);
std::vector< char > vctData;
vctData.resize(lSize + 1, 0);
if (lSize != fread(&vctData[0], 1, lSize, fpIn))
{
LL_WARNS("FSLSLBridge") << "Size mismatch during read" << LL_ENDL;
}
LLFile::close(fpIn);
aBuffer = ( (char const*)&vctData[0] );
const std::string bridgekey = "BRIDGEKEY";
size_t pos = aBuffer.find(bridgekey);
if (pos == std::string::npos)
{
LL_WARNS("FSLSLBridge") << "Invalid bridge script" << LL_ENDL;
return std::string();
}
aBuffer.replace(pos, bridgekey.length(), FSLSLBridge::getInstance()->getBridgeFolder().asString());
LLFILE *fpOut = LLFile::fopen(fNew, "wt");
if (!fpOut)
{
LL_WARNS("FSLSLBridge") << "Cannot open script upload file" << LL_ENDL;
return std::string();
}
if (aBuffer.size() != fwrite(aBuffer.c_str(), 1, aBuffer.size(), fpOut))
{
LL_WARNS("FSLSLBridge") << "Size mismatch during write" << LL_ENDL;
}
LLFile::close(fpOut);
return fNew;
}
void FSLSLBridge::checkBridgeScriptName()
{
if( !mBridgeCreating )
{
LL_WARNS("FSLSLBridge") << "Bridge script length was zero, or bridge was not marked as under creation. Aborting." << LL_ENDL;
return;
}
if (!isBridgeValid())
{
LL_WARNS("FSLSLBridge") << "Bridge not valid (anymore)" << LL_ENDL;
cleanUpBridge();
return;
}
if (!isAgentAvatarValid())
{
LL_WARNS("FSLSLBridge") << "No agent avatar" << LL_ENDL;
cleanUpBridge();
return;
}
//this is our script upload
LLViewerObject* obj = gAgentAvatarp->getWornAttachment(mpBridge->getUUID());
if (!obj)
{
//something happened to our object. Try to fail gracefully.
LL_WARNS("FSLSLBridge") << "Couldn't find worn bridge attachment" << LL_ENDL;
cleanUpBridge();
return;
}
LL_INFOS("FSLSLBridge") << "Saving script " << mScriptItemID.asString() << " in object" << LL_ENDL;
obj->saveScript(gInventory.getItem(mScriptItemID), TRUE, true);
new FSLSLBridgeCleanupTimer();
}
void FSLSLBridge::cleanUpBridge()
{
//something unexpected went wrong. Try to clean up and not crash.
LL_WARNS("FSLSLBridge") << "Bridge object not found. Can't proceed with creation, exiting." << LL_ENDL;
report_to_nearby_chat(LLTrans::getString("fsbridge_failure_not_found"));
if (isBridgeValid())
{
remove_inventory_item(mpBridge->getUUID(), nullptr, true);
}
gInventory.notifyObservers();
mpBridge = nullptr;
mAllowedDetachables.clear();
setBridgeCreating(false);
}
void FSLSLBridge::finishBridge()
{
LL_INFOS("FSLSLBridge") << "Finishing bridge" << LL_ENDL;
setBridgeCreating(false);
mIsFirstCallDone = false;
cleanUpOldVersions();
cleanUpBridgeFolder();
LL_INFOS("FSLSLBridge") << "Bridge cleaned up" << LL_ENDL;
mAllowDetach = true;
mFinishCreation = true;
if (getBridge())
{
LL_INFOS("FSLSLBridge") << "Detaching bridge after cleanup" << LL_ENDL;
LLVOAvatarSelf::detachAttachmentIntoInventory(getBridge()->getUUID());
}
else
{
LL_WARNS("FSLSLBridge") << "Cannot detach bridge - mpBridge = NULL" << LL_ENDL;
}
LL_INFOS("FSLSLBridge") << "End finishing bridge" << LL_ENDL;
}
//
// Helper functions
//
bool FSLSLBridge::isItemAttached(const LLUUID& iID)
{
return (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(iID));
}
void FSLSLBridge::setupFSCategory(inventory_func_type callback)
{
if (LLUUID fsCatID = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); !fsCatID.isNull())
{
LLInventoryModel::item_array_t* items;
LLInventoryModel::cat_array_t* cats;
gInventory.getDirectDescendentsOf(fsCatID, cats, items);
if (cats)
{
for (const auto& cat : *cats)
{
if (cat->getName() == FS_BRIDGE_FOLDER)
{
mBridgeFolderID = cat->getUUID();
callback(mBridgeFolderID);
return;
}
}
}
gInventory.createNewCategory(fsCatID, LLFolderType::FT_NONE, FS_BRIDGE_FOLDER, [this, callback](const LLUUID& new_cat_id)
{
mBridgeFolderID = new_cat_id;
callback(mBridgeFolderID);
});
}
else
{
gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER, [this, callback](const LLUUID& new_cat_id)
{
gInventory.createNewCategory(new_cat_id, LLFolderType::FT_NONE, FS_BRIDGE_FOLDER, [this, callback](const LLUUID& new_cat_id)
{
mBridgeFolderID = new_cat_id;
callback(mBridgeFolderID);
});
});
}
}
// This used to be the place where the bridge folder was also created before it got moved to setupFSCategory.
// We still need this method because it is used in processAttach / processDetach, which (unfortunately) might be called
// before initBridge is called that sets up the bridge folder. But since apparently a bridge is already attached,
// we can assume the folder already exists and we do not need to create it here anymore. And if something is attached
// to where we attach the bridge and also has the same name as the bridge but is in a different folder, it won't make
// any difference if we return the actual category ID or a null UUID for the check performed in processAttach.
LLUUID FSLSLBridge::findFSCategory()
{
if (!mBridgeFolderID.isNull())
{
return mBridgeFolderID;
}
if (LLUUID fsCatID = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); !fsCatID.isNull())
{
LLInventoryModel::item_array_t* items;
LLInventoryModel::cat_array_t* cats;
gInventory.getDirectDescendentsOf(fsCatID, cats, items);
if (cats)
{
for (const auto& cat : *cats)
{
if (cat->getName() == FS_BRIDGE_FOLDER)
{
mBridgeFolderID = cat->getUUID();
return mBridgeFolderID;
}
}
}
}
return LLUUID::null;
}
LLUUID FSLSLBridge::findFSBridgeContainerCategory()
{
LL_INFOS("FSLSLBridge") << "Retrieving FSBridge container category (" << FS_BRIDGE_CONTAINER_FOLDER << ")" << LL_ENDL;
if (mBridgeContainerFolderID.notNull())
{
LL_INFOS("FSLSLBridge") << "Returning FSBridge container category UUID from instance: " << mBridgeContainerFolderID << LL_ENDL;
return mBridgeContainerFolderID;
}
LLUUID LibRootID = gInventory.getLibraryRootFolderID();
if (LibRootID.notNull())
{
LLInventoryModel::item_array_t* items;
LLInventoryModel::cat_array_t* cats;
gInventory.getDirectDescendentsOf(LibRootID, cats, items);
if (cats)
{
for (const auto& cat : *cats)
{
if (cat->getName() == "Objects")
{
const LLUUID& LibObjectsCatID = cat->getUUID();
if (LibObjectsCatID.notNull())
{
LLInventoryModel::item_array_t* objects_items;
LLInventoryModel::cat_array_t* objects_cats;
gInventory.getDirectDescendentsOf(LibObjectsCatID, objects_cats, objects_items);
if (objects_cats)
{
for (const auto& object_cat : *objects_cats)
{
if (object_cat->getName() == FS_BRIDGE_CONTAINER_FOLDER)
{
mBridgeContainerFolderID = object_cat->getUUID();
LL_INFOS("FSLSLBridge") << "FSBridge container category found in library. UUID: " << mBridgeContainerFolderID << LL_ENDL;
gInventory.fetchDescendentsOf(mBridgeContainerFolderID);
return mBridgeContainerFolderID;
}
}
}
}
}
}
}
}
LL_WARNS("FSLSLBridge") << "FSBridge container category not found in library!" << LL_ENDL;
return LLUUID();
}
LLViewerInventoryItem* FSLSLBridge::findInvObject(const std::string& obj_name, const LLUUID& catID)
{
LLViewerInventoryCategory::cat_array_t cats;
LLViewerInventoryItem::item_array_t items;
LLUUID itemID;
NameCollectFunctor namefunctor(obj_name);
gInventory.collectDescendentsIf(catID, cats, items, FALSE, namefunctor);
for (const auto& item : items)
{
if (!item->getIsLinkType() && (item->getType() == LLAssetType::AT_OBJECT))
{
itemID = item->getUUID();
break;
}
}
if (itemID.notNull())
{
LLViewerInventoryItem* item = gInventory.getItem(itemID);
return item;
}
return nullptr;
}
void FSLSLBridge::cleanUpBridgeFolder(const std::string& nameToCleanUp)
{
//LL_INFOS("FSLSLBridge") << "Cleaning leftover scripts and bridges for folder " << nameToCleanUp << LL_ENDL;
if (!isBridgeValid())
{
LL_WARNS("FSLSLBridge") << "Bridge no valid" << LL_ENDL;
return;
}
LLUUID catID = getBridgeFolder();
LLViewerInventoryCategory::cat_array_t cats;
LLViewerInventoryItem::item_array_t items;
//find all bridge and script duplicates and delete them
//NameCollectFunctor namefunctor(mCurrentFullName);
NameCollectFunctor namefunctor(nameToCleanUp);
gInventory.collectDescendentsIf(catID, cats, items, FALSE, namefunctor);
for (const auto& item : items)
{
if (!item->getIsLinkType() && (item->getUUID() != mpBridge->getUUID()))
{
LL_INFOS("FSLSLBridge") << "Bridge folder cleanup: Deleting " << item->getName() << " (" << item->getUUID() << ")" << LL_ENDL;
remove_inventory_item(item->getUUID(), nullptr, true);
}
}
gInventory.notifyObservers();
}
void FSLSLBridge::cleanUpBridgeFolder()
{
cleanUpBridgeFolder(mCurrentFullName);
}
void FSLSLBridge::cleanUpOldVersions()
{
std::string mProcessingName;
for (S32 i = 1; i <= FS_BRIDGE_MAJOR_VERSION; i++)
{
S32 minor_tip;
if (i < FS_BRIDGE_MAJOR_VERSION)
{
minor_tip = FS_MAX_MINOR_VERSION;
}
else
{
minor_tip = FS_BRIDGE_MINOR_VERSION;
}
for (S32 j = 0; j < minor_tip; j++)
{
mProcessingName = llformat("%s%d.%d", FS_BRIDGE_NAME.c_str(), i, j);
cleanUpBridgeFolder(mProcessingName);
}
}
}
void FSLSLBridge::detachOtherBridges()
{
LLUUID catID = getBridgeFolder();
LLViewerInventoryCategory::cat_array_t cats;
LLViewerInventoryItem::item_array_t items;
LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID);
//detach everything except current valid bridge - if any
gInventory.collectDescendents(catID, cats, items, FALSE);
for (const auto& item : items)
{
if (get_is_item_worn(item->getUUID()) &&
((!fsBridge) || (item->getUUID() != fsBridge->getUUID())))
{
LLVOAvatarSelf::detachAttachmentIntoInventory(item->getUUID());
}
}
}
BOOL FSLSLBridgeCleanupTimer::tick()
{
FSLSLBridge::instance().setTimerResult(FSLSLBridge::CLEANUP_FINISHED);
return TRUE;
}
BOOL FSLSLBridgeReAttachTimer::tick()
{
LL_INFOS("FSLSLBridge") << "Re-attaching bridge after creation..." << LL_ENDL;
FSLSLBridge::instance().setTimerResult(FSLSLBridge::REATTACH_FINISHED);
return TRUE;
}
BOOL FSLSLBridgeStartCreationTimer::tick()
{
FSLSLBridge::instance().setTimerResult(FSLSLBridge::START_CREATION_FINISHED);
return TRUE;
}