summaryrefslogtreecommitdiff
path: root/indra/newview/llaisapi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llaisapi.cpp')
-rwxr-xr-xindra/newview/llaisapi.cpp867
1 files changed, 867 insertions, 0 deletions
diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp
new file mode 100755
index 0000000000..38eb34676e
--- /dev/null
+++ b/indra/newview/llaisapi.cpp
@@ -0,0 +1,867 @@
+/**
+ * @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"
+
+///----------------------------------------------------------------------------
+/// Classes for AISv3 support.
+///----------------------------------------------------------------------------
+
+// AISCommand - base class for retry-able HTTP requests using the AISv3 cap.
+AISCommand::AISCommand(LLPointer<LLInventoryCallback> 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(HTTP_INTERNAL_ERROR, "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 LLSD& 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<LLInventoryCallback> callback):
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ return;
+ }
+ std::string url = cap + std::string("/item/") + item_id.asString();
+ LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL;
+ LLHTTPClient::ResponderPtr responder = this;
+ LLSD headers;
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+RemoveCategoryCommand::RemoveCategoryCommand(const LLUUID& item_id,
+ LLPointer<LLInventoryCallback> callback):
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ return;
+ }
+ std::string url = cap + std::string("/category/") + item_id.asString();
+ LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL;
+ LLHTTPClient::ResponderPtr responder = this;
+ LLSD headers;
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+PurgeDescendentsCommand::PurgeDescendentsCommand(const LLUUID& item_id,
+ LLPointer<LLInventoryCallback> callback):
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ return;
+ }
+ std::string url = cap + std::string("/category/") + item_id.asString() + "/children";
+ LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::del, url, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+UpdateItemCommand::UpdateItemCommand(const LLUUID& item_id,
+ const LLSD& updates,
+ LLPointer<LLInventoryCallback> callback):
+ mUpdates(updates),
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ 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;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ headers["Content-Type"] = "application/llsd+xml";
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::patch, url, mUpdates, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+UpdateCategoryCommand::UpdateCategoryCommand(const LLUUID& cat_id,
+ const LLSD& updates,
+ LLPointer<LLInventoryCallback> callback):
+ mUpdates(updates),
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ return;
+ }
+ std::string url = cap + std::string("/category/") + cat_id.asString();
+ LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ headers["Content-Type"] = "application/llsd+xml";
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::patch, url, mUpdates, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+CreateInventoryCommand::CreateInventoryCommand(const LLUUID& parent_id,
+ const LLSD& new_inventory,
+ LLPointer<LLInventoryCallback> callback):
+ mNewInventory(new_inventory),
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ 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;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ headers["Content-Type"] = "application/llsd+xml";
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::post, url, mNewInventory, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+SlamFolderCommand::SlamFolderCommand(const LLUUID& folder_id, const LLSD& contents, LLPointer<LLInventoryCallback> callback):
+ mContents(contents),
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getInvCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ return;
+ }
+ LLUUID tid;
+ tid.generate();
+ std::string url = cap + std::string("/category/") + folder_id.asString() + "/links?tid=" + tid.asString();
+ llinfos << url << llendl;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ headers["Content-Type"] = "application/llsd+xml";
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::put, url, mContents, responder, headers, timeout);
+ setCommandFunc(cmd);
+}
+
+CopyLibraryCategoryCommand::CopyLibraryCategoryCommand(const LLUUID& source_id,
+ const LLUUID& dest_id,
+ LLPointer<LLInventoryCallback> callback):
+ AISCommand(callback)
+{
+ std::string cap;
+ if (!getLibCap(cap))
+ {
+ llwarns << "No cap found" << llendl;
+ 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();
+ llinfos << url << llendl;
+ LLCurl::ResponderPtr responder = this;
+ LLSD headers;
+ F32 timeout = HTTP_REQUEST_EXPIRY_SECS;
+ command_func_type cmd = boost::bind(&LLHTTPClient::copy, url, dest_id.asString(), responder, headers, timeout);
+ 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);
+ mCatDescendentDeltas[cat->getParentUUID()]--;
+ mObjectsDeletedIds.insert(*it);
+ }
+
+ // 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);
+ mCatDescendentDeltas[item->getParentUUID()]--;
+ mObjectsDeletedIds.insert(*it);
+ }
+
+ // 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);
+ mCatDescendentDeltas[item->getParentUUID()]--;
+ mObjectsDeletedIds.insert(*it);
+ }
+
+ // 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<LLViewerInventoryItem> 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?
+ llerrs << "unpack failed" << llendl;
+ }
+}
+
+void AISUpdate::parseLink(const LLSD& link_map)
+{
+ LLUUID item_id = link_map["item_id"].asUUID();
+ LLPointer<LLViewerInventoryItem> 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?
+ llerrs << "unpack failed" << llendl;
+ }
+}
+
+
+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<LLViewerInventoryCategory> 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?
+ llerrs << "unpack failed" << llendl;
+ }
+
+ // 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<LLUUID,S32>::const_iterator catit = mCatDescendentDeltas.begin();
+ catit != mCatDescendentDeltas.end(); ++catit)
+ {
+ LL_DEBUGS("Inventory") << "descendent accounting for " << catit->first << llendl;
+
+ 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<LLViewerInventoryCategory> 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<LLViewerInventoryCategory> 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 (NULL == 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<LLViewerInventoryItem> 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<LLViewerInventoryItem> 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_DEBUGS("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() << llendl;
+ if (cat->getVersion() != version)
+ {
+ llwarns << "Possible version mismatch for category " << cat->getName()
+ << ", viewer version " << cat->getVersion()
+ << " server version " << version << llendl;
+ }
+ }
+
+ gInventory.notifyObservers();
+}
+