diff --git a/indra/llmessage/aicurl.cpp b/indra/llmessage/aicurl.cpp index f9fd07c64..843b06a28 100644 --- a/indra/llmessage/aicurl.cpp +++ b/indra/llmessage/aicurl.cpp @@ -803,6 +803,23 @@ void CurlEasyRequest::setPut(U32 size, bool keepalive) setopt(CURLOPT_INFILESIZE, size); } +void CurlEasyRequest::setPatch(U32 size, bool keepalive) +{ + DoutCurl("PATCH size is " << size << " bytes."); + mContentLength = size; + + // The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default. + addHeader("Expect:"); + if (size > 0 && keepalive) + { + addHeader("Connection: keep-alive"); + addHeader("Keep-alive: 300"); + } + setopt(CURLOPT_UPLOAD, 1); + setopt(CURLOPT_INFILESIZE, size);\ + setopt(CURLOPT_CUSTOMREQUEST, "PATCH"); +} + void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive) { llassert_always(postdata->data()); diff --git a/indra/llmessage/aicurlprivate.h b/indra/llmessage/aicurlprivate.h index cd0bdd963..18855fc94 100644 --- a/indra/llmessage/aicurlprivate.h +++ b/indra/llmessage/aicurlprivate.h @@ -222,6 +222,7 @@ class CurlEasyRequest : public CurlEasyHandle { void setPost_raw(U32 size, char const* data, bool keepalive); public: void setPut(U32 size, bool keepalive = true); + void setPatch(U32 size, bool keepalive = true); void setPost(U32 size, bool keepalive = true) { setPost_raw(size, NULL, keepalive); } void setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive = true); void setPost(char const* data, U32 size, bool keepalive = true) { setPost(new AIPostField(data), size, keepalive); } diff --git a/indra/llmessage/aihttptimeoutpolicy.cpp b/indra/llmessage/aihttptimeoutpolicy.cpp index 86bd75370..9c63a5046 100644 --- a/indra/llmessage/aihttptimeoutpolicy.cpp +++ b/indra/llmessage/aihttptimeoutpolicy.cpp @@ -908,6 +908,7 @@ AIHTTPTimeoutPolicy const* AIHTTPTimeoutPolicy::getTimeoutPolicyByName(std::stri P(assetReportHandler); P(authHandler); P2(baseCapabilitiesComplete, transfer_18s_connect_5s); +P2(baseCapabilitiesCompleteTracker, transfer_18s_connect_5s); P(blockingLLSDPost); P(blockingLLSDGet); P(blockingRawGet); @@ -924,7 +925,6 @@ P(fetchScriptLimitsRegionInfoResponder); P(fetchScriptLimitsRegionSummaryResponder); P(fnPtrResponder); P(floaterPermsResponder); -P2(gamingDataReceived, transfer_22s_connect_10s); P2(groupProposalBallotResponder, transfer_300s); P(homeLocationResponder); P2(HTTPGetResponder, reply_15s); @@ -954,7 +954,7 @@ P(requestAgentUpdateAppearance); P2(incrementCofVersionResponder_timeouts, transfer_30s_connect_10s); P(responderIgnore); P(setDisplayNameResponder); -P2(simulatorFeaturesReceived, transfer_22s_connect_10s); +P2(baseFeaturesReceived, transfer_22s_connect_10s); P2(startGroupVoteResponder, transfer_300s); P(translationReceiver); P(uploadModelPremissionsResponder); diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 863a28469..fbc576234 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -727,6 +727,11 @@ void LLHTTPClient::putRaw(const std::string& url, const U8* data, S32 size, Resp request(url, HTTP_PUT, new RawInjector(data, size), responder, headers, NULL/*,*/ DEBUG_CURLIO_PARAM(debug), no_keep_alive, no_does_authentication, no_allow_compressed_reply); } +void LLHTTPClient::patch(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive, AIStateMachine* parent, AIStateMachine::state_type new_parent_state) +{ + request(url, HTTP_PATCH, new LLSDInjector(body), responder, headers, NULL/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, no_does_authentication, allow_compressed_reply, parent, new_parent_state); +} + void LLHTTPClient::post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive, AIStateMachine* parent, AIStateMachine::state_type new_parent_state) { request(url, HTTP_POST, new LLSDInjector(body), responder, headers, NULL/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, no_does_authentication, allow_compressed_reply, parent, new_parent_state); @@ -780,3 +785,10 @@ void LLHTTPClient::move(std::string const& url, std::string const& destination, headers.addHeader("Destination", destination); request(url, HTTP_MOVE, NULL, responder, headers, NULL/*,*/ DEBUG_CURLIO_PARAM(debug)); } + +// static +void LLHTTPClient::copy(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug)) +{ + headers.addHeader("Destination", destination); + request(url, HTTP_COPY, NULL, responder, headers, NULL/*,*/ DEBUG_CURLIO_PARAM(debug)); +} diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index c0781f331..a7f4cb3eb 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -121,6 +121,8 @@ public: HTTP_POST, HTTP_DELETE, HTTP_MOVE, // Caller will need to set 'Destination' header + HTTP_PATCH, + HTTP_COPY, REQUEST_ACTION_COUNT }; @@ -499,6 +501,10 @@ public: static void getHeaderOnly(std::string const& url, ResponderHeadersOnly* responder/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off)) { AIHTTPHeaders headers; getHeaderOnly(url, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); } + static void patch(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off), EKeepAlive keepalive = keep_alive, AIStateMachine* parent = NULL, U32 new_parent_state = 0); + static void patch(std::string const& url, LLSD const& body, ResponderPtr responder/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off), EKeepAlive keepalive = keep_alive, AIStateMachine* parent = NULL, U32 new_parent_state = 0) + { AIHTTPHeaders headers; patch(url, body, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, parent, new_parent_state); } + static void post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off), EKeepAlive keepalive = keep_alive, AIStateMachine* parent = NULL, U32 new_parent_state = 0); static void post(std::string const& url, LLSD const& body, ResponderPtr responder/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off), EKeepAlive keepalive = keep_alive, AIStateMachine* parent = NULL, U32 new_parent_state = 0) { AIHTTPHeaders headers; post(url, body, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, parent, new_parent_state); } @@ -550,6 +556,12 @@ public: //@} + static void copy(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off)); + static void copy(std::string const& url, std::string const& destination, ResponderPtr responder/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off)) + { + AIHTTPHeaders headers; copy(url, destination, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug)); + } + /** * @brief Blocking HTTP GET that returns an LLSD map of status and body. * diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 85b030785..4fb27892f 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -97,7 +97,7 @@ void LLURLRequest::initialize_impl(void) useProxy(false); } - if (mAction == LLHTTPClient::HTTP_PUT || mAction == LLHTTPClient::HTTP_POST) + if (mAction == LLHTTPClient::HTTP_PUT || mAction == LLHTTPClient::HTTP_POST || mAction == LLHTTPClient::HTTP_PATCH) { // If the Content-Type header was passed in we defer to the caller's wisdom, // but if they did not specify a Content-Type, then ask the injector. @@ -237,7 +237,16 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w) curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : ""); rv = true; break; + + + case LLHTTPClient::HTTP_PATCH: + curlEasyRequest_w->setPatch(mBodySize, mKeepAlive); + + curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : ""); + rv = true; + break; + case LLHTTPClient::HTTP_POST: // Set the handle for an http post @@ -254,6 +263,12 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w) rv = true; break; + case LLHTTPClient::HTTP_COPY: + // Set the handle for an http copy + curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "COPY"); + rv = true; + break; + case LLHTTPClient::HTTP_MOVE: // Set the handle for an http post curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE"); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 8799c86d4..c97092675 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -109,6 +109,7 @@ set(viewer_SOURCE_FILES lggdicdownload.cpp lgghunspell_wrapper.cpp llaccountingcostmanager.cpp + llaisapi.cpp llagent.cpp llagentaccess.cpp llagentcamera.cpp @@ -639,6 +640,7 @@ set(viewer_HEADER_FILES lggdicdownload.h lgghunspell_wrapper.h llaccountingcostmanager.h + llaisapi.h llagent.h llagentaccess.h llagentcamera.h diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp new file mode 100644 index 000000000..257cc628c --- /dev/null +++ b/indra/newview/llaisapi.cpp @@ -0,0 +1,877 @@ +/** + * @file llaisapi.cpp + * @brief classes and functions for interfacing with the v3+ ais inventory service. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "llviewerprecompiledheaders.h" +#include "llaisapi.h" + +#include "llagent.h" +#include "llcallbacklist.h" +#include "llinventorymodel.h" +#include "llsdutil.h" +#include "llviewerregion.h" +#include "llinventoryobserver.h" +#include "llviewercontrol.h" + +///---------------------------------------------------------------------------- +/// Classes for AISv3 support. +///---------------------------------------------------------------------------- + +// AISCommand - base class for retry-able HTTP requests using the AISv3 cap. +AISCommand::AISCommand(LLPointer callback): + mCommandFunc(NULL), + mCallback(callback) +{ + mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 32.0, 2.0, 10); +} + +bool AISCommand::run_command() +{ + if (NULL == mCommandFunc) + { + // This may happen if a command failed to initiate itself. + LL_WARNS("Inventory") << "AIS command attempted with null command function" << LL_ENDL; + return false; + } + else + { + mCommandFunc(); + return true; + } +} + +void AISCommand::setCommandFunc(command_func_type command_func) +{ + mCommandFunc = command_func; +} + +// virtual +bool AISCommand::getResponseUUID(const LLSD& content, LLUUID& id) +{ + return false; +} + +/* virtual */ +void AISCommand::httpSuccess() +{ + // Command func holds a reference to self, need to release it + // after a success or final failure. + setCommandFunc(no_op); + + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(400, "Malformed response contents", content); + return; + } + mRetryPolicy->onSuccess(); + + gInventory.onAISUpdateReceived("AISCommand", content); + + if (mCallback) + { + LLUUID id; // will default to null if parse fails. + getResponseUUID(content,id); + mCallback->fire(id); + } +} + +/*virtual*/ +void AISCommand::httpFailure() +{ + LL_WARNS("Inventory") << dumpResponse() << LL_ENDL; + S32 status = getStatus(); + const AIHTTPReceivedHeaders& headers = getResponseHeaders(); + mRetryPolicy->onFailure(status, headers); + F32 seconds_to_wait; + if (mRetryPolicy->shouldRetry(seconds_to_wait)) + { + doAfterInterval(boost::bind(&AISCommand::run_command,this),seconds_to_wait); + } + else + { + // Command func holds a reference to self, need to release it + // after a success or final failure. + // *TODO: Notify user? This seems bad. + setCommandFunc(no_op); + } +} + +//static +bool AISCommand::isAPIAvailable() +{ + if (gAgent.getRegion()) + { + return gAgent.getRegion()->isCapabilityAvailable("InventoryAPIv3"); + } + return false; +} + +//static +bool AISCommand::getInvCap(std::string& cap) +{ + if (gAgent.getRegion()) + { + cap = gAgent.getRegion()->getCapability("InventoryAPIv3"); + } + if (!cap.empty()) + { + return true; + } + return false; +} + +//static +bool AISCommand::getLibCap(std::string& cap) +{ + if (gAgent.getRegion()) + { + cap = gAgent.getRegion()->getCapability("LibraryAPIv3"); + } + if (!cap.empty()) + { + return true; + } + return false; +} + +//static +void AISCommand::getCapabilityNames(LLSD& capabilityNames) +{ + capabilityNames.append("InventoryAPIv3"); + capabilityNames.append("LibraryAPIv3"); +} + +RemoveItemCommand::RemoveItemCommand(const LLUUID& item_id, + LLPointer callback): + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + std::string url = cap + std::string("/item/") + item_id.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off)); + setCommandFunc(cmd); +} + +RemoveCategoryCommand::RemoveCategoryCommand(const LLUUID& item_id, + LLPointer callback): + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + std::string url = cap + std::string("/category/") + item_id.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off)); + setCommandFunc(cmd); +} + +PurgeDescendentsCommand::PurgeDescendentsCommand(const LLUUID& item_id, + LLPointer callback): + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + std::string url = cap + std::string("/category/") + item_id.asString() + "/children"; + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off)); + setCommandFunc(cmd); +} + +UpdateItemCommand::UpdateItemCommand(const LLUUID& item_id, + const LLSD& updates, + LLPointer callback): + mUpdates(updates), + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + std::string url = cap + std::string("/item/") + item_id.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LL_DEBUGS("Inventory") << "request: " << ll_pretty_print_sd(mUpdates) << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + AIHTTPHeaders headers; + headers.addHeader("Content-Type", "application/llsd+xml"); + command_func_type cmd = boost::bind(&LLHTTPClient::patch, url, mUpdates, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off), keep_alive, (AIStateMachine*)NULL, 0); + setCommandFunc(cmd); +} + +UpdateCategoryCommand::UpdateCategoryCommand(const LLUUID& cat_id, + const LLSD& updates, + LLPointer callback): + mUpdates(updates), + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + std::string url = cap + std::string("/category/") + cat_id.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + AIHTTPHeaders headers; + headers.addHeader("Content-Type", "application/llsd+xml"); + command_func_type cmd = boost::bind(&LLHTTPClient::patch, url, mUpdates, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off), keep_alive, (AIStateMachine*)NULL, 0); + setCommandFunc(cmd); +} + +CreateInventoryCommand::CreateInventoryCommand(const LLUUID& parent_id, + const LLSD& new_inventory, + LLPointer callback): + mNewInventory(new_inventory), + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + LLUUID tid; + tid.generate(); + std::string url = cap + std::string("/category/") + parent_id.asString() + "?tid=" + tid.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + AIHTTPHeaders headers; + headers.addHeader("Content-Type", "application/llsd+xml"); + command_func_type cmd = boost::bind(&LLHTTPClient::post, url, mNewInventory, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off), keep_alive, (AIStateMachine*)NULL, 0); + setCommandFunc(cmd); +} + +SlamFolderCommand::SlamFolderCommand(const LLUUID& folder_id, const LLSD& contents, LLPointer callback): + mContents(contents), + AISCommand(callback) +{ + std::string cap; + if (!getInvCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + LLUUID tid; + tid.generate(); + std::string url = cap + std::string("/category/") + folder_id.asString() + "/links?tid=" + tid.asString(); + LL_INFOS() << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + AIHTTPHeaders headers; + headers.addHeader("Content-Type", "application/llsd+xml"); + command_func_type cmd = boost::bind(&LLHTTPClient::put, url, mContents, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug_off)); + setCommandFunc(cmd); +} + +CopyLibraryCategoryCommand::CopyLibraryCategoryCommand(const LLUUID& source_id, + const LLUUID& dest_id, + LLPointer callback): + AISCommand(callback) +{ + std::string cap; + if (!getLibCap(cap)) + { + LL_WARNS() << "No cap found" << LL_ENDL; + return; + } + LL_DEBUGS("Inventory") << "Copying library category: " << source_id << " => " << dest_id << LL_ENDL; + LLUUID tid; + tid.generate(); + std::string url = cap + std::string("/category/") + source_id.asString() + "?tid=" + tid.asString(); + LL_INFOS() << url << LL_ENDL; + LLHTTPClient::ResponderPtr responder = this; + command_func_type cmd = boost::bind(&LLHTTPClient::copy, url, dest_id.asString(), responder); + setCommandFunc(cmd); +} + +bool CopyLibraryCategoryCommand::getResponseUUID(const LLSD& content, LLUUID& id) +{ + if (content.has("category_id")) + { + id = content["category_id"]; + return true; + } + return false; +} + +AISUpdate::AISUpdate(const LLSD& update) +{ + parseUpdate(update); +} + +void AISUpdate::clearParseResults() +{ + mCatDescendentDeltas.clear(); + mCatDescendentsKnown.clear(); + mCatVersionsUpdated.clear(); + mItemsCreated.clear(); + mItemsUpdated.clear(); + mCategoriesCreated.clear(); + mCategoriesUpdated.clear(); + mObjectsDeletedIds.clear(); + mItemIds.clear(); + mCategoryIds.clear(); +} + +void AISUpdate::parseUpdate(const LLSD& update) +{ + clearParseResults(); + parseMeta(update); + parseContent(update); +} + +void AISUpdate::parseMeta(const LLSD& update) +{ + // parse _categories_removed -> mObjectsDeletedIds + uuid_list_t cat_ids; + parseUUIDArray(update,"_categories_removed",cat_ids); + for (uuid_list_t::const_iterator it = cat_ids.begin(); + it != cat_ids.end(); ++it) + { + LLViewerInventoryCategory *cat = gInventory.getCategory(*it); + if(cat) + { + mCatDescendentDeltas[cat->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "removed category not found " << *it << LL_ENDL; + } + } + + // parse _categories_items_removed -> mObjectsDeletedIds + uuid_list_t item_ids; + parseUUIDArray(update,"_category_items_removed",item_ids); + parseUUIDArray(update,"_removed_items",item_ids); + for (uuid_list_t::const_iterator it = item_ids.begin(); + it != item_ids.end(); ++it) + { + LLViewerInventoryItem *item = gInventory.getItem(*it); + if(item) + { + mCatDescendentDeltas[item->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "removed item not found " << *it << LL_ENDL; + } + } + + // parse _broken_links_removed -> mObjectsDeletedIds + uuid_list_t broken_link_ids; + parseUUIDArray(update,"_broken_links_removed",broken_link_ids); + for (uuid_list_t::const_iterator it = broken_link_ids.begin(); + it != broken_link_ids.end(); ++it) + { + LLViewerInventoryItem *item = gInventory.getItem(*it); + if(item) + { + mCatDescendentDeltas[item->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "broken link not found " << *it << LL_ENDL; + } + } + + // parse _created_items + parseUUIDArray(update,"_created_items",mItemIds); + + // parse _created_categories + parseUUIDArray(update,"_created_categories",mCategoryIds); + + // Parse updated category versions. + const std::string& ucv = "_updated_category_versions"; + if (update.has(ucv)) + { + for(LLSD::map_const_iterator it = update[ucv].beginMap(), + end = update[ucv].endMap(); + it != end; ++it) + { + const LLUUID id((*it).first); + S32 version = (*it).second.asInteger(); + mCatVersionsUpdated[id] = version; + } + } +} + +void AISUpdate::parseContent(const LLSD& update) +{ + if (update.has("linked_id")) + { + parseLink(update); + } + else if (update.has("item_id")) + { + parseItem(update); + } + + if (update.has("category_id")) + { + parseCategory(update); + } + else + { + if (update.has("_embedded")) + { + parseEmbedded(update["_embedded"]); + } + } +} + +void AISUpdate::parseItem(const LLSD& item_map) +{ + LLUUID item_id = item_map["item_id"].asUUID(); + LLPointer new_item(new LLViewerInventoryItem); + LLViewerInventoryItem *curr_item = gInventory.getItem(item_id); + if (curr_item) + { + // Default to current values where not provided. + new_item->copyViewerItem(curr_item); + } + BOOL rv = new_item->unpackMessage(item_map); + if (rv) + { + if (curr_item) + { + mItemsUpdated[item_id] = new_item; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[new_item->getParentUUID()]; + } + else + { + mItemsCreated[item_id] = new_item; + mCatDescendentDeltas[new_item->getParentUUID()]++; + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } +} + +void AISUpdate::parseLink(const LLSD& link_map) +{ + LLUUID item_id = link_map["item_id"].asUUID(); + LLPointer new_link(new LLViewerInventoryItem); + LLViewerInventoryItem *curr_link = gInventory.getItem(item_id); + if (curr_link) + { + // Default to current values where not provided. + new_link->copyViewerItem(curr_link); + } + BOOL rv = new_link->unpackMessage(link_map); + if (rv) + { + const LLUUID& parent_id = new_link->getParentUUID(); + if (curr_link) + { + mItemsUpdated[item_id] = new_link; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[parent_id]; + } + else + { + LLPermissions default_perms; + default_perms.init(gAgent.getID(),gAgent.getID(),LLUUID::null,LLUUID::null); + default_perms.initMasks(PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE); + new_link->setPermissions(default_perms); + LLSaleInfo default_sale_info; + new_link->setSaleInfo(default_sale_info); + //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; + mItemsCreated[item_id] = new_link; + mCatDescendentDeltas[parent_id]++; + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } +} + + +void AISUpdate::parseCategory(const LLSD& category_map) +{ + LLUUID category_id = category_map["category_id"].asUUID(); + + // Check descendent count first, as it may be needed + // to populate newly created categories + if (category_map.has("_embedded")) + { + parseDescendentCount(category_id, category_map["_embedded"]); + } + + LLPointer new_cat(new LLViewerInventoryCategory(category_id)); + LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); + if (curr_cat) + { + // Default to current values where not provided. + new_cat->copyViewerCategory(curr_cat); + } + BOOL rv = new_cat->unpackMessage(category_map); + // *NOTE: unpackMessage does not unpack version or descendent count. + //if (category_map.has("version")) + //{ + // mCatVersionsUpdated[category_id] = category_map["version"].asInteger(); + //} + if (rv) + { + if (curr_cat) + { + mCategoriesUpdated[category_id] = new_cat; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[new_cat->getParentUUID()]; + // Capture update for the category itself as well. + mCatDescendentDeltas[category_id]; + } + else + { + // Set version/descendents for newly created categories. + if (category_map.has("version")) + { + S32 version = category_map["version"].asInteger(); + LL_DEBUGS("Inventory") << "Setting version to " << version + << " for new category " << category_id << LL_ENDL; + new_cat->setVersion(version); + } + uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); + if (mCatDescendentsKnown.end() != lookup_it) + { + S32 descendent_count = lookup_it->second; + LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count + << " for new category " << category_id << LL_ENDL; + new_cat->setDescendentCount(descendent_count); + } + mCategoriesCreated[category_id] = new_cat; + mCatDescendentDeltas[new_cat->getParentUUID()]++; + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } + + // Check for more embedded content. + if (category_map.has("_embedded")) + { + parseEmbedded(category_map["_embedded"]); + } +} + +void AISUpdate::parseDescendentCount(const LLUUID& category_id, const LLSD& embedded) +{ + // We can only determine true descendent count if this contains all descendent types. + if (embedded.has("categories") && + embedded.has("links") && + embedded.has("items")) + { + mCatDescendentsKnown[category_id] = embedded["categories"].size(); + mCatDescendentsKnown[category_id] += embedded["links"].size(); + mCatDescendentsKnown[category_id] += embedded["items"].size(); + } +} + +void AISUpdate::parseEmbedded(const LLSD& embedded) +{ + if (embedded.has("links")) // _embedded in a category + { + parseEmbeddedLinks(embedded["links"]); + } + if (embedded.has("items")) // _embedded in a category + { + parseEmbeddedItems(embedded["items"]); + } + if (embedded.has("item")) // _embedded in a link + { + parseEmbeddedItem(embedded["item"]); + } + if (embedded.has("categories")) // _embedded in a category + { + parseEmbeddedCategories(embedded["categories"]); + } + if (embedded.has("category")) // _embedded in a link + { + parseEmbeddedCategory(embedded["category"]); + } +} + +void AISUpdate::parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids) +{ + if (content.has(name)) + { + for(LLSD::array_const_iterator it = content[name].beginArray(), + end = content[name].endArray(); + it != end; ++it) + { + ids.insert((*it).asUUID()); + } + } +} + +void AISUpdate::parseEmbeddedLinks(const LLSD& links) +{ + for(LLSD::map_const_iterator linkit = links.beginMap(), + linkend = links.endMap(); + linkit != linkend; ++linkit) + { + const LLUUID link_id((*linkit).first); + const LLSD& link_map = (*linkit).second; + if (mItemIds.end() == mItemIds.find(link_id)) + { + LL_DEBUGS("Inventory") << "Ignoring link not in items list " << link_id << LL_ENDL; + } + else + { + parseLink(link_map); + } + } +} + +void AISUpdate::parseEmbeddedItem(const LLSD& item) +{ + // a single item (_embedded in a link) + if (item.has("item_id")) + { + if (mItemIds.end() != mItemIds.find(item["item_id"].asUUID())) + { + parseItem(item); + } + } +} + +void AISUpdate::parseEmbeddedItems(const LLSD& items) +{ + // a map of items (_embedded in a category) + for(LLSD::map_const_iterator itemit = items.beginMap(), + itemend = items.endMap(); + itemit != itemend; ++itemit) + { + const LLUUID item_id((*itemit).first); + const LLSD& item_map = (*itemit).second; + if (mItemIds.end() == mItemIds.find(item_id)) + { + LL_DEBUGS("Inventory") << "Ignoring item not in items list " << item_id << LL_ENDL; + } + else + { + parseItem(item_map); + } + } +} + +void AISUpdate::parseEmbeddedCategory(const LLSD& category) +{ + // a single category (_embedded in a link) + if (category.has("category_id")) + { + if (mCategoryIds.end() != mCategoryIds.find(category["category_id"].asUUID())) + { + parseCategory(category); + } + } +} + +void AISUpdate::parseEmbeddedCategories(const LLSD& categories) +{ + // a map of categories (_embedded in a category) + for(LLSD::map_const_iterator categoryit = categories.beginMap(), + categoryend = categories.endMap(); + categoryit != categoryend; ++categoryit) + { + const LLUUID category_id((*categoryit).first); + const LLSD& category_map = (*categoryit).second; + if (mCategoryIds.end() == mCategoryIds.find(category_id)) + { + LL_DEBUGS("Inventory") << "Ignoring category not in categories list " << category_id << LL_ENDL; + } + else + { + parseCategory(category_map); + } + } +} + +void AISUpdate::doUpdate() +{ + // Do version/descendent accounting. + for (std::map::const_iterator catit = mCatDescendentDeltas.begin(); + catit != mCatDescendentDeltas.end(); ++catit) + { + LL_DEBUGS("Inventory") << "descendent accounting for " << catit->first << LL_ENDL; + + const LLUUID cat_id(catit->first); + // Don't account for update if we just created this category. + if (mCategoriesCreated.find(cat_id) != mCategoriesCreated.end()) + { + LL_DEBUGS("Inventory") << "Skipping version increment for new category " << cat_id << LL_ENDL; + continue; + } + + // Don't account for update unless AIS told us it updated that category. + if (mCatVersionsUpdated.find(cat_id) == mCatVersionsUpdated.end()) + { + LL_DEBUGS("Inventory") << "Skipping version increment for non-updated category " << cat_id << LL_ENDL; + continue; + } + + // If we have a known descendent count, set that now. + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + S32 descendent_delta = catit->second; + S32 old_count = cat->getDescendentCount(); + LL_DEBUGS("Inventory") << "Updating descendent count for " + << cat->getName() << " " << cat_id + << " with delta " << descendent_delta << " from " + << old_count << " to " << (old_count+descendent_delta) << LL_ENDL; + LLInventoryModel::LLCategoryUpdate up(cat_id, descendent_delta); + gInventory.accountForUpdate(up); + } + else + { + LL_DEBUGS("Inventory") << "Skipping version accounting for unknown category " << cat_id << LL_ENDL; + } + } + + // CREATE CATEGORIES + for (deferred_category_map_t::const_iterator create_it = mCategoriesCreated.begin(); + create_it != mCategoriesCreated.end(); ++create_it) + { + LLUUID category_id(create_it->first); + LLPointer new_category = create_it->second; + + gInventory.updateCategory(new_category, LLInventoryObserver::CREATE); + LL_DEBUGS("Inventory") << "created category " << category_id << LL_ENDL; + } + + // UPDATE CATEGORIES + for (deferred_category_map_t::const_iterator update_it = mCategoriesUpdated.begin(); + update_it != mCategoriesUpdated.end(); ++update_it) + { + LLUUID category_id(update_it->first); + LLPointer new_category = update_it->second; + // Since this is a copy of the category *before* the accounting update, above, + // we need to transfer back the updated version/descendent count. + LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID()); + if (!curr_cat) + { + LL_WARNS("Inventory") << "Failed to update unknown category " << new_category->getUUID() << LL_ENDL; + } + else + { + new_category->setVersion(curr_cat->getVersion()); + new_category->setDescendentCount(curr_cat->getDescendentCount()); + gInventory.updateCategory(new_category); + LL_DEBUGS("Inventory") << "updated category " << new_category->getName() << " " << category_id << LL_ENDL; + } + } + + // CREATE ITEMS + for (deferred_item_map_t::const_iterator create_it = mItemsCreated.begin(); + create_it != mItemsCreated.end(); ++create_it) + { + LLUUID item_id(create_it->first); + LLPointer new_item = create_it->second; + + // FIXME risky function since it calls updateServer() in some + // cases. Maybe break out the update/create cases, in which + // case this is create. + LL_DEBUGS("Inventory") << "created item " << item_id << LL_ENDL; + gInventory.updateItem(new_item, LLInventoryObserver::CREATE); + } + + // UPDATE ITEMS + for (deferred_item_map_t::const_iterator update_it = mItemsUpdated.begin(); + update_it != mItemsUpdated.end(); ++update_it) + { + LLUUID item_id(update_it->first); + LLPointer new_item = update_it->second; + // FIXME risky function since it calls updateServer() in some + // cases. Maybe break out the update/create cases, in which + // case this is update. + LL_DEBUGS("Inventory") << "updated item " << item_id << LL_ENDL; + //LL_DEBUGS("Inventory") << ll_pretty_print_sd(new_item->asLLSD()) << LL_ENDL; + gInventory.updateItem(new_item); + } + + // DELETE OBJECTS + for (uuid_list_t::const_iterator del_it = mObjectsDeletedIds.begin(); + del_it != mObjectsDeletedIds.end(); ++del_it) + { + LL_INFOS("Inventory") << "deleted item " << *del_it << LL_ENDL; + gInventory.onObjectDeletedFromServer(*del_it, false, false, false); + } + + // TODO - how can we use this version info? Need to be sure all + // changes are going through AIS first, or at least through + // something with a reliable responder. + for (uuid_int_map_t::iterator ucv_it = mCatVersionsUpdated.begin(); + ucv_it != mCatVersionsUpdated.end(); ++ucv_it) + { + const LLUUID id = ucv_it->first; + S32 version = ucv_it->second; + LLViewerInventoryCategory *cat = gInventory.getCategory(id); + LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL; + if (cat->getVersion() != version) + { + LL_WARNS() << "Possible version mismatch for category " << cat->getName() + << ", viewer version " << cat->getVersion() + << " server version " << version << LL_ENDL; + } + } + + gInventory.notifyObservers(); +} + diff --git a/indra/newview/llaisapi.h b/indra/newview/llaisapi.h new file mode 100644 index 000000000..3225630ec --- /dev/null +++ b/indra/newview/llaisapi.h @@ -0,0 +1,194 @@ +/** + * @file llaisapi.h + * @brief classes and functions for interfacing with the v3+ ais inventory service. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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_LLAISAPI_H +#define LL_LLAISAPI_H + +#include "lluuid.h" +#include +#include +#include +#include "llcurl.h" +#include "llhttpclient.h" +#include "llhttpretrypolicy.h" +#include "llviewerinventory.h" + +class AISCommand: public LLHTTPClient::ResponderWithResult +{ +public: + typedef boost::function command_func_type; + + AISCommand(LLPointer callback); + + virtual ~AISCommand() {} + + bool run_command(); + + void setCommandFunc(command_func_type command_func); + + // Need to do command-specific parsing to get an id here, for + // LLInventoryCallback::fire(). May or may not need to bother, + // since most LLInventoryCallbacks do their work in the + // destructor. + + /* virtual */ void httpSuccess(); + /* virtual */ void httpFailure(); + + static bool isAPIAvailable(); + static bool getInvCap(std::string& cap); + static bool getLibCap(std::string& cap); + static void getCapabilityNames(LLSD& capabilityNames); + +protected: + virtual bool getResponseUUID(const LLSD& content, LLUUID& id); + +private: + command_func_type mCommandFunc; + LLPointer mRetryPolicy; + LLPointer mCallback; +}; + +class RemoveItemCommand: public AISCommand +{ +public: + RemoveItemCommand(const LLUUID& item_id, + LLPointer callback); + /* virtual */ const char* getName() const { return "RemoveItemCommand"; } +}; + +class RemoveCategoryCommand: public AISCommand +{ +public: + RemoveCategoryCommand(const LLUUID& item_id, + LLPointer callback); + /* virtual */ const char* getName() const { return "RemoveCategoryCommand"; } +}; + +class PurgeDescendentsCommand: public AISCommand +{ +public: + PurgeDescendentsCommand(const LLUUID& item_id, + LLPointer callback); + /* virtual */ const char* getName() const { return "PurgeDescendentsCommand"; } + +}; + +class UpdateItemCommand: public AISCommand +{ +public: + UpdateItemCommand(const LLUUID& item_id, + const LLSD& updates, + LLPointer callback); + /* virtual */ const char* getName() const { return "UpdateItemCommand"; } + +private: + LLSD mUpdates; +}; + +class UpdateCategoryCommand: public AISCommand +{ +public: + UpdateCategoryCommand(const LLUUID& cat_id, + const LLSD& updates, + LLPointer callback); + /* virtual */ const char* getName() const { return "UpdateCategoryCommand"; } + +private: + LLSD mUpdates; +}; + +class SlamFolderCommand: public AISCommand +{ +public: + SlamFolderCommand(const LLUUID& folder_id, const LLSD& contents, LLPointer callback); + /* virtual */ const char* getName() const { return "SlamFolderCommand"; } + +private: + LLSD mContents; +}; + +class CopyLibraryCategoryCommand: public AISCommand +{ +public: + CopyLibraryCategoryCommand(const LLUUID& source_id, const LLUUID& dest_id, LLPointer callback); + /* virtual */ const char* getName() const { return "CopyLibraryCategoryCommand"; } + +protected: + /* virtual */ bool getResponseUUID(const LLSD& content, LLUUID& id); +}; + +class CreateInventoryCommand: public AISCommand +{ +public: + CreateInventoryCommand(const LLUUID& parent_id, const LLSD& new_inventory, LLPointer callback); + /* virtual */ const char* getName() const { return "CreateInventoryCommand"; } + +private: + LLSD mNewInventory; +}; + +class AISUpdate +{ +public: + AISUpdate(const LLSD& update); + void parseUpdate(const LLSD& update); + void parseMeta(const LLSD& update); + void parseContent(const LLSD& update); + void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); + void parseLink(const LLSD& link_map); + void parseItem(const LLSD& link_map); + void parseCategory(const LLSD& link_map); + void parseDescendentCount(const LLUUID& category_id, const LLSD& embedded); + void parseEmbedded(const LLSD& embedded); + void parseEmbeddedLinks(const LLSD& links); + void parseEmbeddedItems(const LLSD& items); + void parseEmbeddedCategories(const LLSD& categories); + void parseEmbeddedItem(const LLSD& item); + void parseEmbeddedCategory(const LLSD& category); + void doUpdate(); +private: + void clearParseResults(); + + typedef std::map uuid_int_map_t; + uuid_int_map_t mCatDescendentDeltas; + uuid_int_map_t mCatDescendentsKnown; + uuid_int_map_t mCatVersionsUpdated; + + typedef std::map > deferred_item_map_t; + deferred_item_map_t mItemsCreated; + deferred_item_map_t mItemsUpdated; + typedef std::map > deferred_category_map_t; + deferred_category_map_t mCategoriesCreated; + deferred_category_map_t mCategoriesUpdated; + + // These keep track of uuid's mentioned in meta values. + // Useful for filtering out which content we are interested in. + uuid_list_t mObjectsDeletedIds; + uuid_list_t mItemIds; + uuid_list_t mCategoryIds; +}; + +#endif diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 35d9b360b..acac6e739 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -51,6 +51,7 @@ #include "llwearablelist.h" #include "llsdutil.h" #include "llhttpretrypolicy.h" +#include "llaisapi.h" #include "llinventorypanel.h" #include "llfloatercustomize.h" // [RLVa:KB] - Checked: 2011-05-22 (RLVa-1.3.1a) @@ -2791,6 +2792,25 @@ void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName() << " )" << LL_ENDL; + // If we are copying from library, attempt to use AIS to copy the category. + bool ais_ran=false; + if (copy && AISCommand::isAPIAvailable()) + { + LLUUID parent_id; + parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + if (parent_id.isNull()) + { + parent_id = gInventory.getRootFolderID(); + } + + LLPointer copy_cb = new LLWearCategoryAfterCopy(append); + LLPointer track_cb = new LLTrackPhaseWrapper( + std::string("wear_inventory_category_callback"), copy_cb); + boost::intrusive_ptr cmd_ptr = new CopyLibraryCategoryCommand(category->getUUID(), parent_id, track_cb); + ais_ran=cmd_ptr->run_command(); + } + + if (!ais_ran) { selfStartPhase("wear_inventory_category_fetch"); callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal, diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 28fe64695..5c417a8b4 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -5951,7 +5951,7 @@ void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { disabled_items.push_back(std::string("Wearable Add")); LLViewerWearable* wearable = gAgentWearables.getWearableFromAssetID(item->getAssetUUID()); - if (cof_pending || (wearable && wearable != gAgentWearables.getTopWearable(mWearableType))) + if ((wearable && wearable != gAgentWearables.getTopWearable(mWearableType))) disabled_items.push_back(std::string("Wearable And Object Wear")); } // [RLVa:KB] - Checked: 2010-06-09 (RLVa-1.2.0g) | Modified: RLVa-1.2.0g diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 732db0aac..26a8678e2 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llinventorymodel.h" +#include "llaisapi.h" #include "llagent.h" #include "llagentwearables.h" #include "llappearancemgr.h" @@ -1292,6 +1293,19 @@ void LLInventoryModel::changeCategoryParent(LLViewerInventoryCategory* cat, notifyObservers(); } +void LLInventoryModel::onAISUpdateReceived(const std::string& context, const LLSD& update) +{ + LLTimer timer; + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); + } + + AISUpdate ais_update(update); // parse update llsd into stuff to do. + ais_update.doUpdate(); // execute the updates in the appropriate order. + LL_INFOS(LOG_INV) << "elapsed: " << timer.getElapsedTimeF32() << LL_ENDL; +} + // Does not appear to be used currently. void LLInventoryModel::onItemUpdated(const LLUUID& item_id, const LLSD& updates, bool update_parent_version) { diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 20bbafe5a..b9187ef6f 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -367,6 +367,10 @@ public: // Delete //-------------------------------------------------------------------- public: + + // Update model after an AISv3 update received for any operation. + void onAISUpdateReceived(const std::string& context, const LLSD& update); + // Update model after an item is confirmed as removed from // server. Works for categories or items. void onObjectDeletedFromServer(const LLUUID& item_id, diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 989a6bb6f..ee4a0d8bd 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -31,6 +31,7 @@ #include "llsdserialize.h" #include "message.h" +#include "llaisapi.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentwearables.h" @@ -1224,6 +1225,17 @@ void link_inventory_array(const LLUUID& category, << " UUID:" << category << " ] " << LL_ENDL; #endif } + + bool ais_ran = false; + if (AISCommand::isAPIAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["links"] = links; + boost::intrusive_ptr cmd_ptr = new CreateInventoryCommand(category, new_inventory, cb); + ais_ran = cmd_ptr->run_command(); + } + + if (!ais_ran) { LLMessageSystem* msg = gMessageSystem; for (LLSD::array_iterator iter = links.beginArray(); iter != links.endArray(); ++iter ) @@ -1280,6 +1292,25 @@ void update_inventory_item( LLPointer cb) { const LLUUID& item_id = update_item->getUUID(); + bool ais_ran = false; + if (AISCommand::isAPIAvailable()) + { + LLSD updates = update_item->asLLSD(); + // Replace asset_id and/or shadow_id with transaction_id (hash_id) + if (updates.has("asset_id")) + { + updates.erase("asset_id"); + updates["hash_id"] = update_item->getTransactionID(); + } + if (updates.has("shadow_id")) + { + updates.erase("shadow_id"); + updates["hash_id"] = update_item->getTransactionID(); + } + boost::intrusive_ptr cmd_ptr = new UpdateItemCommand(item_id, updates, cb); + ais_ran = cmd_ptr->run_command(); + } + if (!ais_ran) { LLPointer obj = gInventory.getItem(item_id); LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << (update_item ? update_item->getName() : "(NOT FOUND)") << LL_ENDL; @@ -1331,6 +1362,13 @@ void update_inventory_item( } // [/SL:KB] + bool ais_ran = false; + if (AISCommand::isAPIAvailable()) + { + boost::intrusive_ptr cmd_ptr = new UpdateItemCommand(item_id, updates, cb); + ais_ran = cmd_ptr->run_command(); + } + if (!ais_ran) { // LLPointer obj = gInventory.getItem(item_id); // LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << LL_ENDL; @@ -1383,6 +1421,14 @@ void update_inventory_category( LLPointer new_cat = new LLViewerInventoryCategory(obj); new_cat->fromLLSD(updates); + // FIXME - restore this once the back-end work has been done. + if (AISCommand::isAPIAvailable()) + { + LLSD new_llsd = new_cat->asLLSD(); + boost::intrusive_ptr cmd_ptr = new UpdateCategoryCommand(cat_id, new_llsd, cb); + cmd_ptr->run_command(); + } + else // no cap { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_UpdateInventoryFolder); @@ -1442,6 +1488,17 @@ void remove_inventory_item( { const LLUUID item_id(obj->getUUID()); LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << obj->getName() << LL_ENDL; + if (AISCommand::isAPIAvailable()) + { + boost::intrusive_ptr cmd_ptr = new RemoveItemCommand(item_id, cb); + cmd_ptr->run_command(); + + if (immediate_delete) + { + gInventory.onObjectDeletedFromServer(item_id); + } + } + else // no cap { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveInventoryItem); @@ -1507,6 +1564,12 @@ void remove_inventory_category( LLNotificationsUtil::add("CannotRemoveProtectedCategories"); return; } + if (AISCommand::isAPIAvailable()) + { + boost::intrusive_ptr cmd_ptr = new RemoveCategoryCommand(cat_id, cb); + cmd_ptr->run_command(); + } + else // no cap { // RemoveInventoryFolder does not remove children, so must // clear descendents first. @@ -1604,6 +1667,12 @@ void purge_descendents_of(const LLUUID& id, LLPointer cb) } else { + if (AISCommand::isAPIAvailable()) + { + boost::intrusive_ptr cmd_ptr = new PurgeDescendentsCommand(id, cb); + cmd_ptr->run_command(); + } + else // no cap { // Fast purge LL_DEBUGS(LOG_INV) << "purge_descendents_of fast case " << cat->getName() << LL_ENDL; @@ -1814,6 +1883,14 @@ void slam_inventory_folder(const LLUUID& folder_id, const LLSD& contents, LLPointer cb) { + if (AISCommand::isAPIAvailable()) + { + LL_DEBUGS(LOG_INV) << "using AISv3 to slam folder, id " << folder_id + << " new contents: " << ll_pretty_print_sd(contents) << LL_ENDL; + boost::intrusive_ptr cmd_ptr = new SlamFolderCommand(folder_id, contents, cb); + cmd_ptr->run_command(); + } + else // no cap { // [RLVa:KB] - Checked: 2014-11-02 (RLVa-1.4.11) LL_DEBUGS(LOG_INV) << "using item-by-item calls to slam folder, id " << folder_id diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index ade30dbe3..486a1776e 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -36,6 +36,7 @@ // linden libraries #include "indra_constants.h" +#include "llaisapi.h" #include "llavatarnamecache.h" // name lookup cap url //#include "llfloaterreg.h" #include "llmath.h" @@ -89,8 +90,8 @@ class AIHTTPTimeoutPolicy; extern AIHTTPTimeoutPolicy baseCapabilitiesComplete_timeout; -extern AIHTTPTimeoutPolicy gamingDataReceived_timeout; -extern AIHTTPTimeoutPolicy simulatorFeaturesReceived_timeout; +extern AIHTTPTimeoutPolicy baseCapabilitiesCompleteTracker_timeout; +extern AIHTTPTimeoutPolicy baseFeaturesReceived_timeout; const F32 WATER_TEXTURE_SCALE = 8.f; // Number of times to repeat the water texture across a region const S16 MAX_MAP_DIST = 10; @@ -103,6 +104,8 @@ const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; typedef std::map CapabilityMap; +static void log_capabilities(const CapabilityMap &capmap); + class LLViewerRegionImpl { public: LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host) @@ -158,6 +161,7 @@ public: LLUUID mCacheID; CapabilityMap mCapabilities; + CapabilityMap mSecondCapabilitiesTracker; LLEventPoll* mEventPoll; @@ -219,15 +223,25 @@ class BaseCapabilitiesComplete : public LLHTTPClient::ResponderWithResult { LOG_CLASS(BaseCapabilitiesComplete); public: - BaseCapabilitiesComplete(U64 region_handle, S32 id) + BaseCapabilitiesComplete(U64 region_handle, S32 id) : mRegionHandle(region_handle), mID(id) - { } + { } virtual ~BaseCapabilitiesComplete() { } + static boost::intrusive_ptr build( U64 region_handle, S32 id ) + { + return boost::intrusive_ptr( + new BaseCapabilitiesComplete(region_handle, id) ); + } + + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return baseCapabilitiesComplete_timeout; } + /*virtual*/ char const* getName(void) const { return "BaseCapabilitiesComplete"; } + +private: void httpFailure(void) { - LL_WARNS("AppInit", "Capabilities") << mStatus << ": " << mReason << LL_ENDL; + LL_WARNS("AppInit", "Capabilities") << dumpResponse() << LL_ENDL; LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); if (regionp) { @@ -246,15 +260,23 @@ public: if( mID != regionp->getHttpResponderID() ) // region is no longer referring to this responder { LL_WARNS("AppInit", "Capabilities") << "Received results for a stale http responder!" << LL_ENDL; + regionp->failedSeedCapability(); return ; } + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(400, "Malformed response contents", content); + return; + } LLSD::map_const_iterator iter; - for(iter = mContent.beginMap(); iter != mContent.endMap(); ++iter) + for(iter = content.beginMap(); iter != content.endMap(); ++iter) { regionp->setCapability(iter->first, iter->second); - LL_DEBUGS("AppInit", "Capabilities") << "got capability for " - << iter->first << LL_ENDL; + + LL_DEBUGS("AppInit", "Capabilities") + << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL; /* HACK we're waiting for the ServerReleaseNotes */ if (iter->first == "ServerReleaseNotes" && regionp->getReleaseNotesRequested()) @@ -271,20 +293,103 @@ public: } } - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return baseCapabilitiesComplete_timeout; } - /*virtual*/ char const* getName(void) const { return "BaseCapabilitiesComplete"; } - - static boost::intrusive_ptr build( U64 region_handle, S32 id ) - { - return boost::intrusive_ptr( - new BaseCapabilitiesComplete(region_handle, id) ); - } - private: U64 mRegionHandle; S32 mID; }; +class BaseCapabilitiesCompleteTracker : public LLHTTPClient::ResponderWithResult +{ + LOG_CLASS(BaseCapabilitiesCompleteTracker); +public: + BaseCapabilitiesCompleteTracker( U64 region_handle) + : mRegionHandle(region_handle) + { } + + virtual ~BaseCapabilitiesCompleteTracker() + { } + + static boost::intrusive_ptr build( U64 region_handle ) + { + return boost::intrusive_ptr( + new BaseCapabilitiesCompleteTracker(region_handle)); + } + + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return baseCapabilitiesCompleteTracker_timeout; } + /*virtual*/ char const* getName(void) const { return "BaseCapabilitiesCompleteTracker"; } + +private: + /* virtual */ void httpFailure() + { + LL_WARNS() << dumpResponse() << LL_ENDL; + } + + /* virtual */ void httpSuccess() + { + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if( !regionp ) + { + LL_WARNS("AppInit", "Capabilities") << "Received results for region that no longer exists!" << LL_ENDL; + return ; + } + + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(400, "Malformed response contents", content); + return; + } + LLSD::map_const_iterator iter; + for(iter = content.beginMap(); iter != content.endMap(); ++iter) + { + regionp->setCapabilityDebug(iter->first, iter->second); + //LL_INFOS()<<"BaseCapabilitiesCompleteTracker New Caps "<first<<" "<< iter->second<getRegionImpl()->mCapabilities.size() != regionp->getRegionImpl()->mSecondCapabilitiesTracker.size() ) + { + LL_WARNS("AppInit", "Capabilities") + << "Sim sent duplicate base caps that differ in size from what we initially received - most likely content. " + << "mCapabilities == " << regionp->getRegionImpl()->mCapabilities.size() + << " mSecondCapabilitiesTracker == " << regionp->getRegionImpl()->mSecondCapabilitiesTracker.size() + << LL_ENDL; +//#ifdef DEBUG_CAPS_GRANTS + LL_WARNS("AppInit", "Capabilities") + << "Initial Base capabilities: " << LL_ENDL; + + log_capabilities(regionp->getRegionImpl()->mCapabilities); + + LL_WARNS("AppInit", "Capabilities") + << "Latest base capabilities: " << LL_ENDL; + + log_capabilities(regionp->getRegionImpl()->mSecondCapabilitiesTracker); + +//#endif + + if (regionp->getRegionImpl()->mSecondCapabilitiesTracker.size() > regionp->getRegionImpl()->mCapabilities.size() ) + { + // *HACK Since we were granted more base capabilities in this grant request than the initial, replace + // the old with the new. This shouldn't happen i.e. we should always get the same capabilities from a + // sim. The simulator fix from SH-3895 should prevent it from happening, at least in the case of the + // inventory api capability grants. + + // Need to clear a std::map before copying into it because old keys take precedence. + regionp->getRegionImplNC()->mCapabilities.clear(); + regionp->getRegionImplNC()->mCapabilities = regionp->getRegionImpl()->mSecondCapabilitiesTracker; + } + } + else + { + LL_DEBUGS("CrossingCaps") << "Sim sent multiple base cap grants with matching sizes." << LL_ENDL; + } + regionp->getRegionImplNC()->mSecondCapabilitiesTracker.clear(); + } + + +private: + U64 mRegionHandle; +}; + LLViewerRegion::LLViewerRegion(const U64 &handle, const LLHost &host, @@ -1786,6 +1891,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("FetchInventoryDescendents2"); capabilityNames.append("IncrementCOFVersion"); capabilityNames.append("GamingData"); //Used by certain grids. + AISCommand::getCapabilityNames(capabilityNames); capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetMesh"); capabilityNames.append("GetMesh2"); // Used on SecondLife(tm) sim versions 280647 and higher (13.09.17). @@ -1851,8 +1957,16 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) void LLViewerRegion::setSeedCapability(const std::string& url) { if (getCapability("Seed") == url) - { - // LL_WARNS() << "Ignoring duplicate seed capability" << LL_ENDL; + { + setCapabilityDebug("Seed", url); + LL_DEBUGS("CrossingCaps") << "Received duplicate seed capability, posting to seed " << + url << LL_ENDL; + + //Instead of just returning we build up a second set of seed caps and compare them + //to the "original" seed cap received and determine why there is problem! + LLSD capabilityNames = LLSD::emptyArray(); + mImpl->buildCapabilityNames( capabilityNames ); + LLHTTPClient::post( url, capabilityNames, BaseCapabilitiesCompleteTracker::build(getHandle() )); return; } @@ -1913,19 +2027,19 @@ void LLViewerRegion::failedSeedCapability() } } -class SimulatorFeaturesReceived : public LLHTTPClient::ResponderWithResult +class BaseFeaturesReceived : public LLHTTPClient::ResponderWithResult { - LOG_CLASS(SimulatorFeaturesReceived); + LOG_CLASS(BaseFeaturesReceived); public: - SimulatorFeaturesReceived(const std::string& retry_url, U64 region_handle, + BaseFeaturesReceived(const std::string& retry_url, U64 region_handle, const char* classname, boost::function fn, S32 attempt = 0, S32 max_attempts = MAX_CAP_REQUEST_ATTEMPTS) - : mRetryURL(retry_url), mRegionHandle(region_handle), mAttempt(attempt), mMaxAttempts(max_attempts) + : mRetryURL(retry_url), mRegionHandle(region_handle), mAttempt(attempt), mMaxAttempts(max_attempts), mClassName(classname), mFunction(fn) { } void httpFailure(void) { - LL_WARNS("AppInit", "SimulatorFeatures") << mStatus << ": " << mReason << LL_ENDL; + LL_WARNS("AppInit", mClassName) << dumpResponse() << LL_ENDL; retry(); } @@ -1934,24 +2048,32 @@ public: LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); if(!regionp) //region is removed or responder is not created. { - LL_WARNS("AppInit", "SimulatorFeatures") << "Received results for region that no longer exists!" << LL_ENDL; + LL_WARNS("AppInit", mClassName) + << "Received results for region that no longer exists!" << LL_ENDL; return ; } - regionp->setSimulatorFeatures(mContent); + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(400, "Malformed response contents", content); + return; + } + mFunction(regionp, content); } - - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return simulatorFeaturesReceived_timeout; } - /*virtual*/ char const* getName(void) const { return "SimulatorFeaturesReceived"; } + + /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return baseFeaturesReceived_timeout; } + /*virtual*/ char const* getName(void) const { return mClassName; } private: + void retry() { if (mAttempt < mMaxAttempts) { mAttempt++; - LL_WARNS("AppInit", "SimulatorFeatures") << "Re-trying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; - LLHTTPClient::get(mRetryURL, new SimulatorFeaturesReceived(*this)); + LL_WARNS("AppInit", mClassName) << "Re-trying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; + LLHTTPClient::get(mRetryURL, new BaseFeaturesReceived(*this)); } } @@ -1959,49 +2081,10 @@ private: U64 mRegionHandle; S32 mAttempt; S32 mMaxAttempts; + const char* mClassName; + boost::function mFunction; }; -class GamingDataReceived : public LLHTTPClient::ResponderWithResult -{ - LOG_CLASS(GamingDataReceived); -public: - GamingDataReceived(const std::string& retry_url, U64 region_handle, S32 attempt = 0, S32 max_attempts = MAX_CAP_REQUEST_ATTEMPTS) - : mRetryURL(retry_url), mRegionHandle(region_handle), mAttempt(attempt), mMaxAttempts(max_attempts) - {} - - /*virtual*/ void httpFailure(void) - { - LL_WARNS("AppInit", "GamingData") << mStatus << ": " << mReason << LL_ENDL; - retry(); - } - - /*virtual*/ void httpSuccess(void) - { - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); - if(regionp) regionp->setGamingData(mContent); - } - - /*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return gamingDataReceived_timeout; } - /*virtual*/ char const* getName(void) const { return "GamingDataReceived"; } - -private: - void retry() - { - if (mAttempt < mMaxAttempts) - { - mAttempt++; - LL_WARNS("AppInit", "GamingData") << "Retrying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; - LLHTTPClient::get(mRetryURL, new GamingDataReceived(*this)); - } - } - - std::string mRetryURL; - U64 mRegionHandle; - S32 mAttempt; - S32 mMaxAttempts; -}; - - void LLViewerRegion::setCapability(const std::string& name, const std::string& url) { if(name == "EventQueueGet") @@ -2020,13 +2103,13 @@ void LLViewerRegion::setCapability(const std::string& name, const std::string& u mImpl->mCapabilities[name] = url; // kick off a request for simulator features - LLHTTPClient::get(url, new SimulatorFeaturesReceived(url, getHandle())); + LLHTTPClient::get(url, new BaseFeaturesReceived(url, getHandle(), "SimulatorFeaturesReceived", &LLViewerRegion::setSimulatorFeatures)); } else if (name == "GamingData") { LLSD gamingRequest = LLSD::emptyMap(); // request settings from simulator - LLHTTPClient::post(url, gamingRequest, new GamingDataReceived(url, getHandle())); + LLHTTPClient::post(url, gamingRequest, new BaseFeaturesReceived(url, getHandle(), "GamingDataReceived", &LLViewerRegion::setGamingData)); } else { @@ -2038,6 +2121,20 @@ void LLViewerRegion::setCapability(const std::string& name, const std::string& u } } +void LLViewerRegion::setCapabilityDebug(const std::string& name, const std::string& url) +{ + // Continue to not add certain caps, as we do in setCapability. This is so they match up when we check them later. + if ( ! ( name == "EventQueueGet" || name == "UntrustedSimulatorMessage" || name == "SimulatorFeatures" ) ) + { + mImpl->mSecondCapabilitiesTracker[name] = url; + if(name == "GetTexture") + { + mHttpUrl = url ; + } + } + +} + bool LLViewerRegion::isSpecialCapabilityName(const std::string &name) { return name == "EventQueueGet" || name == "UntrustedSimulatorMessage"; @@ -2047,7 +2144,7 @@ std::string LLViewerRegion::getCapability(const std::string& name) const { if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) { - LL_WARNS() << "getCapability("<mCapabilities.find(name); @@ -2059,6 +2156,22 @@ std::string LLViewerRegion::getCapability(const std::string& name) const return iter->second; } +bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const +{ + if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) + { + LL_WARNS() << "isCapabilityAvailable called before caps received for " << name << LL_ENDL; + } + + CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name); + if(iter == mImpl->mCapabilities.end()) + { + return false; + } + + return true; +} + bool LLViewerRegion::capabilitiesReceived() const { return mCapabilitiesReceived; @@ -2210,7 +2323,21 @@ bool LLViewerRegion::avatarHoverHeightEnabled() const return ( mSimulatorFeatures.has("AvatarHoverHeightEnabled") && mSimulatorFeatures["AvatarHoverHeightEnabled"].asBoolean()); } +/* Static Functions */ +void log_capabilities(const CapabilityMap &capmap) +{ + S32 count = 0; + CapabilityMap::const_iterator iter; + for (iter = capmap.begin(); iter != capmap.end(); ++iter, ++count) + { + if (!iter->second.empty()) + { + LL_INFOS() << "log_capabilities: " << iter->first << " URL is " << iter->second << LL_ENDL; + } + } + LL_INFOS() << "log_capabilities: Dumped " << count << " entries." << LL_ENDL; +} void LLViewerRegion::resetMaterialsCapThrottle() { F32 requests_per_sec = 1.0f; // original default; diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 044d60149..4bc0bc8aa 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -272,6 +272,8 @@ public: void failedSeedCapability(); S32 getNumSeedCapRetries(); void setCapability(const std::string& name, const std::string& url); + void setCapabilityDebug(const std::string& name, const std::string& url); + bool isCapabilityAvailable(const std::string& name) const; // implements LLCapabilityProvider virtual std::string getCapability(const std::string& name) const; @@ -377,7 +379,9 @@ public: void getNeighboringRegions( std::vector& uniqueRegions ); void getNeighboringRegionsStatus( std::vector& regions ); - + const LLViewerRegionImpl * getRegionImpl() const { return mImpl; } + LLViewerRegionImpl * getRegionImplNC() { return mImpl; } + void setGamingData(const LLSD& info); const U32 getGamingFlags() const { return mGamingFlags; }