From 6c56c77ec575141963c5de8dfa228253fe175bc3 Mon Sep 17 00:00:00 2001
From: "Brad Payne (Vir Linden)" <vir@lindenlab.com>
Date: Fri, 24 May 2013 08:53:21 -0400
Subject: [PATCH] SH-4027 WIP - initial implementation of item update via AIS.

---
 indra/llinventory/llinventory.cpp      |   2 +-
 indra/llinventory/llinventory.h        |   4 +-
 indra/llmessage/llhttpclient.cpp       |  12 +-
 indra/llmessage/llhttpclient.h         |   8 +
 indra/llmessage/llhttpconstants.h      |  37 +-
 indra/llmessage/llurlrequest.cpp       |  11 +
 indra/newview/llappearancemgr.cpp      |   4 +-
 indra/newview/llinventorybridge.cpp    |  10 +-
 indra/newview/llinventoryfunctions.cpp |   9 +-
 indra/newview/llinventorymodel.cpp     |  93 ++++-
 indra/newview/llinventorymodel.h       |   6 +
 indra/newview/llviewerinventory.cpp    | 510 +++++++++++++++++--------
 indra/newview/llviewerinventory.h      |  14 +
 13 files changed, 523 insertions(+), 197 deletions(-)
 mode change 100644 => 100755 indra/llmessage/llhttpconstants.h

diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp
index a4cd8333cf2..641532ec29e 100755
--- a/indra/llinventory/llinventory.cpp
+++ b/indra/llinventory/llinventory.cpp
@@ -261,7 +261,7 @@ void LLInventoryObject::updateServer(BOOL) const
 	llwarns << "LLInventoryObject::updateServer() called.  Doesn't do anything." << llendl;
 }
 
-inline
+// static
 void LLInventoryObject::correctInventoryName(std::string& name)
 {
 	LLStringUtil::replaceNonstandardASCII(name, ' ');
diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h
index 8b865f044d7..17421b3f5e7 100755
--- a/indra/llinventory/llinventory.h
+++ b/indra/llinventory/llinventory.h
@@ -86,16 +86,14 @@ class LLInventoryObject : public LLRefCount
 	void setType(LLAssetType::EType type);
 	virtual void setCreationDate(time_t creation_date_utc); // only stored for items
 
-private:
 	// in place correction for inventory name string
-	void correctInventoryName(std::string& name);
+	static void correctInventoryName(std::string& name);
 
 	//--------------------------------------------------------------------
 	// File Support
 	//   Implemented here so that a minimal information set can be transmitted
 	//   between simulator and viewer.
 	//--------------------------------------------------------------------
-public:
 	// virtual BOOL importFile(LLFILE* fp);
 	virtual BOOL exportFile(LLFILE* fp, BOOL include_asset_key = TRUE) const;
 	virtual BOOL importLegacyStream(std::istream& input_stream);
diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp
index 5830a5eca01..53cef545592 100755
--- a/indra/llmessage/llhttpclient.cpp
+++ b/indra/llmessage/llhttpclient.cpp
@@ -304,7 +304,7 @@ static void request(
 					gMessageSystem->mPort));
 	}
 
-	if (method == HTTP_PUT || method == HTTP_POST)
+	if (method == HTTP_PUT || method == HTTP_POST || method == HTTP_PATCH)
 	{
 		if(!headers.has(HTTP_OUT_HEADER_CONTENT_TYPE))
 		{
@@ -556,6 +556,16 @@ void LLHTTPClient::put(
 	request(url, HTTP_PUT, new LLSDInjector(body), responder, timeout, headers);
 }
 
+void LLHTTPClient::patch(
+	const std::string& url,
+	const LLSD& body,
+	ResponderPtr responder,
+	const LLSD& headers,
+	const F32 timeout)
+{
+	request(url, HTTP_PATCH, new LLSDInjector(body), responder, timeout, headers);
+}
+
 void LLHTTPClient::post(
 	const std::string& url,
 	const LLSD& body,
diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h
index a7236ba1699..4e7495495fd 100755
--- a/indra/llmessage/llhttpclient.h
+++ b/indra/llmessage/llhttpclient.h
@@ -74,6 +74,14 @@ class LLHTTPClient
 		ResponderPtr,
 		const LLSD& headers = LLSD(),
 		const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+
+	static void patch(
+		const std::string& url,
+		const LLSD& body,
+		ResponderPtr,
+		const LLSD& headers = LLSD(),
+		const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
+
 	static void getHeaderOnly(const std::string& url, ResponderPtr, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
 	static void getHeaderOnly(const std::string& url, ResponderPtr, const LLSD& headers, const F32 timeout=HTTP_REQUEST_EXPIRY_SECS);
 
diff --git a/indra/llmessage/llhttpconstants.h b/indra/llmessage/llhttpconstants.h
old mode 100644
new mode 100755
index 8cc3459654a..aa947af414b
--- a/indra/llmessage/llhttpconstants.h
+++ b/indra/llmessage/llhttpconstants.h
@@ -2,31 +2,25 @@
  * @file llhttpconstants.h
  * @brief Constants for HTTP requests and responses
  *
- * $LicenseInfo:firstyear=2001&license=viewergpl$
- * 
- * Copyright (c) 2001-2013, Linden Research, Inc.
- * 
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab.  Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * Copyright (C) 2001-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.
  * 
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 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.
  * 
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
+ * 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
  * 
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  * $/LicenseInfo$
  */
 
@@ -117,6 +111,7 @@ enum EHTTPMethod
 	HTTP_DELETE,
 	HTTP_MOVE, // Caller will need to set 'Destination' header
 	HTTP_OPTIONS,
+	HTTP_PATCH,
 	HTTP_METHOD_COUNT
 };
 
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
index 49cfef27716..cadff49cb8d 100755
--- a/indra/llmessage/llurlrequest.cpp
+++ b/indra/llmessage/llurlrequest.cpp
@@ -487,6 +487,17 @@ bool LLURLRequest::configure()
 		rv = true;
 		break;
 
+	case HTTP_PATCH:
+		// Disable the expect http 1.1 extension. POST and PUT default
+		// to turning this on, and I am not too sure what it means.
+		addHeader(HTTP_OUT_HEADER_EXPECT);
+
+		mDetail->mCurlRequest->setopt(CURLOPT_UPLOAD, 1);
+		mDetail->mCurlRequest->setopt(CURLOPT_INFILESIZE, bytes);
+		mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "PATCH");
+		rv = true;
+		break;
+
 	case HTTP_POST:
 		// Disable the expect http 1.1 extension. POST and PUT default
 		// to turning this on, and I am not too sure what it means.
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index cfe9055aab3..14eed6e1df9 100755
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -2812,8 +2812,10 @@ struct WearablesOrderComparator
 		//items with ordering information but not for the associated wearables type
 		if (!item1_valid && item2_valid) 
 			return false;
+		else if (item1_valid && !item2_valid)
+			return true;
 
-		return true;
+		return item1->getName() < item2->getName();
 	}
 
 	U32 mControlSize;
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index c32abe507e0..89c56ab82c9 100755
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -1692,13 +1692,9 @@ BOOL LLItemBridge::renameItem(const std::string& new_name)
 	LLViewerInventoryItem* item = getItem();
 	if(item && (item->getName() != new_name))
 	{
-		LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
-		new_item->rename(new_name);
-		new_item->updateServer(FALSE);
-		model->updateItem(new_item);
-
-		model->notifyObservers();
-		buildDisplayName();
+		LLSD updates;
+		updates["name"] = new_name;
+		update_inventory_item(item->getUUID(),updates, NULL);
 	}
 	// return FALSE because we either notified observers (& therefore
 	// rebuilt) or we didn't update.
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index f1a4889f5ac..b5fb2268727 100755
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -123,12 +123,9 @@ void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::s
 		return;
 	}
 
-	LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat);
-	new_cat->rename(new_name);
-	new_cat->updateServer(FALSE);
-	model->updateCategory(new_cat);
-
-	model->notifyObservers();
+	LLSD updates;
+	updates["name"] = new_name;
+	update_inventory_category(cat_id, updates, NULL);
 }
 
 void copy_inventory_category(LLInventoryModel* model,
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 73ef3e60da3..06c614aeaad 100755
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -1196,6 +1196,36 @@ void LLInventoryModel::onAISUpdateReceived(const std::string& context, const LLS
 		onObjectDeletedFromServer(*it, false);
 	}
 
+	if (update.has("item_id"))
+	{
+		// item has been modified or possibly created (would be better if we could distinguish these cases directly)
+		LLUUID item_id = update["item_id"].asUUID();
+		LLViewerInventoryItem *item = gInventory.getItem(item_id);
+		LLViewerInventoryCategory *cat = gInventory.getCategory(item_id);
+		if (item)
+		{
+			LLSD changes;
+			if (update.has("name") && update["name"] != item->getName())
+			{
+				changes["name"] = update["name"];
+			}
+			if (update.has("desc") && update["desc"] != item->getActualDescription())
+			{
+				changes["desc"] = update["desc"];
+			}
+			onItemUpdated(item_id,changes);
+		}
+		else if (cat)
+		{
+			llerrs << "don't handle cat update yet" << llendl;
+		}
+		else
+		{
+			llerrs << "don't handle creation case yet" << llendl;
+		}
+	
+	}
+
 	// 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.
@@ -1212,10 +1242,71 @@ void LLInventoryModel::onAISUpdateReceived(const std::string& context, const LLS
 		}
 	}
 #endif
-
 	
 }
 
+void LLInventoryModel::onItemUpdated(const LLUUID& item_id, const LLSD& updates)
+{
+	U32 mask = LLInventoryObserver::NONE;
+
+	LLPointer<LLViewerInventoryItem> item = gInventory.getItem(item_id);
+	LL_DEBUGS("Inventory") << "item_id: [" << item_id << "] name " << (item ? item->getName() : "(NOT FOUND)") << llendl;
+	if(item)
+	{
+		for (LLSD::map_const_iterator it = updates.beginMap();
+			 it != updates.endMap(); ++it)
+		{
+			if (it->first == "name")
+			{
+				llinfos << "Updating name from " << item->getName() << " to " << it->second.asString() << llendl;
+				item->rename(it->second.asString());
+				mask |= LLInventoryObserver::LABEL;
+			}
+			else if (it->first == "desc")
+			{
+				llinfos << "Updating description from " << item->getActualDescription()
+						<< " to " << it->second.asString() << llendl;
+				item->setDescription(it->second.asString());
+			}
+			else
+			{
+				llerrs << "unhandled updates for field: " << it->first << llendl;
+			}
+		}
+		mask |= LLInventoryObserver::INTERNAL;
+		addChangedMask(mask, item->getUUID());
+		gInventory.notifyObservers(); // do we want to be able to make this optional?
+	}
+}
+
+void LLInventoryModel::onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates)
+{
+	U32 mask = LLInventoryObserver::NONE;
+
+	LLPointer<LLViewerInventoryCategory> cat = gInventory.getCategory(cat_id);
+	LL_DEBUGS("Inventory") << "cat_id: [" << cat_id << "] name " << (cat ? cat->getName() : "(NOT FOUND)") << llendl;
+	if(cat)
+	{
+		for (LLSD::map_const_iterator it = updates.beginMap();
+			 it != updates.endMap(); ++it)
+		{
+			if (it->first == "name")
+			{
+				llinfos << "Updating name from " << cat->getName() << " to " << it->second.asString() << llendl;
+				cat->rename(it->second.asString());
+				mask |= LLInventoryObserver::LABEL;
+			}
+			else
+			{
+				llerrs << "unhandled updates for field: " << it->first << llendl;
+			}
+		}
+		mask |= LLInventoryObserver::INTERNAL;
+		addChangedMask(mask, cat->getUUID());
+		gInventory.notifyObservers(); // do we want to be able to make this optional?
+	}
+}
+
 // Update model after descendents have been purged.
 void LLInventoryModel::onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links)
 {
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index 696d0a91632..515c99c0b40 100755
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -339,6 +339,12 @@ class LLInventoryModel
 	// Update model after all descendents removed from server.
 	void onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links = true);
 
+	// Update model after an existing item gets updated on server.
+	void onItemUpdated(const LLUUID& item_id, const LLSD& updates);
+
+	// Update model after an existing category gets updated on server.
+	void onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates);
+
 	// Delete a particular inventory object by ID. Will purge one
 	// object from the internal data structures, maintaining a
 	// consistent internal state. No cache accounting, observer
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 50d67463c7b..90fef3b5edd 100755
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -259,6 +259,240 @@ class LLInventoryHandler : public LLCommandHandler
 LLInventoryHandler gInventoryHandler;
 
 
+///----------------------------------------------------------------------------
+/// Classes for AISv3 support.
+///----------------------------------------------------------------------------
+class AISCommand: public LLHTTPClient::Responder
+{
+public:
+	typedef boost::function<void()> command_func_type;
+
+	AISCommand(LLPointer<LLInventoryCallback> callback):
+		mCallback(callback)
+	{
+		mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 32.0, 2.0, 10);
+	}
+
+	virtual ~AISCommand()
+	{
+	}
+
+	void run_command()
+	{
+		mCommandFunc();
+	}
+
+	void setCommandFunc(command_func_type command_func)
+	{
+		mCommandFunc = command_func;
+	}
+	
+	// Need to do command-specific parsing to get an id here.  May or
+	// may not need to bother, since most LLInventoryCallbacks do
+	// their work in the destructor.
+	virtual bool getResponseUUID(const LLSD& content, LLUUID& id)
+	{
+		return false;
+	}
+	
+	/* virtual */ void 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 item_id; // will default to null if parse fails.
+			getResponseUUID(content,item_id);
+			mCallback->fire(item_id);
+		}
+	}
+
+	/*virtual*/ void httpFailure()
+	{
+		const LLSD& content = getContent();
+		S32 status = getStatus();
+		const std::string& reason = getReason();
+		const LLSD& headers = getResponseHeaders();
+		if (!content.isMap())
+		{
+			LL_DEBUGS("Inventory") << "Malformed response contents " << content
+								   << " status " << status << " reason " << reason << llendl;
+		}
+		else
+		{
+			LL_DEBUGS("Inventory") << "failed with content: " << ll_pretty_print_sd(content)
+								   << " status " << status << " reason " << reason << llendl;
+		}
+		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.
+			setCommandFunc(no_op);
+		}
+	}
+
+	static bool getCap(std::string& cap)
+	{
+		if (gAgent.getRegion())
+		{
+			cap = gAgent.getRegion()->getCapability("InventoryAPIv3");
+		}
+		if (!cap.empty())
+		{
+			return true;
+		}
+		return false;
+	}
+
+private:
+	command_func_type mCommandFunc;
+	LLPointer<LLHTTPRetryPolicy> mRetryPolicy;
+	LLPointer<LLInventoryCallback> mCallback;
+};
+
+class RemoveItemCommand: public AISCommand
+{
+public:
+	RemoveItemCommand(const LLUUID& item_id,
+					  LLPointer<LLInventoryCallback> callback):
+		AISCommand(callback)
+	{
+		std::string cap;
+		if (!getCap(cap))
+		{
+			llwarns << "No cap found" << llendl;
+			return;
+		}
+		std::string url = cap + std::string("/item/") + item_id.asString();
+		LL_DEBUGS("Inventory") << "url: " << url << llendl;
+		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);
+	}
+};
+
+class RemoveCategoryCommand: public AISCommand
+{
+public:
+	RemoveCategoryCommand(const LLUUID& item_id,
+						  LLPointer<LLInventoryCallback> callback):
+		AISCommand(callback)
+	{
+		std::string cap;
+		if (!getCap(cap))
+		{
+			llwarns << "No cap found" << llendl;
+			return;
+		}
+		std::string url = cap + std::string("/category/") + item_id.asString();
+		LL_DEBUGS("Inventory") << "url: " << url << llendl;
+		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);
+	}
+};
+
+class PurgeDescendentsCommand: public AISCommand
+{
+public:
+	PurgeDescendentsCommand(const LLUUID& item_id,
+							LLPointer<LLInventoryCallback> callback):
+		AISCommand(callback)
+	{
+		std::string cap;
+		if (!getCap(cap))
+		{
+			llwarns << "No cap found" << llendl;
+			return;
+		}
+		std::string url = cap + std::string("/category/") + item_id.asString() + "/children";
+		LL_DEBUGS("Inventory") << "url: " << url << llendl;
+		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);
+	}
+};
+
+class UpdateItemCommand: public AISCommand
+{
+public:
+	UpdateItemCommand(const LLUUID& item_id,
+					  const LLSD& updates,
+					  LLPointer<LLInventoryCallback> callback):
+		mUpdates(updates),
+		AISCommand(callback)
+	{
+		std::string cap;
+		if (!getCap(cap))
+		{
+			llwarns << "No cap found" << llendl;
+			return;
+		}
+		std::string url = cap + std::string("/item/") + item_id.asString();
+		LL_DEBUGS("Inventory") << "url: " << 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::patch, url, mUpdates, responder, headers, timeout);
+		setCommandFunc(cmd);
+	}
+private:
+	LLSD mUpdates;
+};
+
+class UpdateCategoryCommand: public AISCommand
+{
+public:
+	UpdateCategoryCommand(const LLUUID& item_id,
+						  const LLSD& updates,
+						  LLPointer<LLInventoryCallback> callback):
+		mUpdates(updates),
+		AISCommand(callback)
+	{
+		std::string cap;
+		if (!getCap(cap))
+		{
+			llwarns << "No cap found" << llendl;
+			return;
+		}
+		std::string url = cap + std::string("/category/") + item_id.asString();
+		LL_DEBUGS("Inventory") << "url: " << 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::patch, url, mUpdates, responder, headers, timeout);
+		setCommandFunc(cmd);
+	}
+private:
+	LLSD mUpdates;
+};
+
 ///----------------------------------------------------------------------------
 /// Class LLViewerInventoryItem
 ///----------------------------------------------------------------------------
@@ -456,6 +690,20 @@ void LLViewerInventoryItem::setTransactionID(const LLTransactionID& transaction_
 // virtual
 void LLViewerInventoryItem::packMessage(LLMessageSystem* msg) const
 {
+	static const LLSD updates;
+	packUpdateMessage(msg,updates);
+}
+
+void LLViewerInventoryItem::packUpdateMessage(LLMessageSystem* msg, const LLSD& updates) const
+{
+	for (LLSD::map_const_iterator it = updates.beginMap(); it != updates.endMap(); ++it)
+	{
+		if ((it->first != "desc") && (it->first != "name"))
+		{
+			llerrs << "unhandled field: " << it->first << llendl;
+		}
+	}
+	
 	msg->addUUIDFast(_PREHASH_ItemID, mUUID);
 	msg->addUUIDFast(_PREHASH_FolderID, mParentUUID);
 	mPermissions.packMessage(msg);
@@ -466,12 +714,29 @@ void LLViewerInventoryItem::packMessage(LLMessageSystem* msg) const
 	msg->addS8Fast(_PREHASH_InvType, type);
 	msg->addU32Fast(_PREHASH_Flags, mFlags);
 	mSaleInfo.packMessage(msg);
-	msg->addStringFast(_PREHASH_Name, mName);
-	msg->addStringFast(_PREHASH_Description, mDescription);
+	if (updates.has("name"))
+	{
+		std::string new_name = updates["name"].asString();
+		LLInventoryObject::correctInventoryName(new_name);
+		msg->addStringFast(_PREHASH_Name, new_name);
+	}
+	else
+	{
+		msg->addStringFast(_PREHASH_Name, mName);
+	}
+	if (updates.has("desc"))
+	{
+		msg->addStringFast(_PREHASH_Description, updates["desc"].asString());
+	}
+	else
+	{
+		msg->addStringFast(_PREHASH_Description, mDescription);
+	}
 	msg->addS32Fast(_PREHASH_CreationDate, mCreationDate);
 	U32 crc = getCRC32();
 	msg->addU32Fast(_PREHASH_CRC, crc);
 }
+
 // virtual
 BOOL LLViewerInventoryItem::importFile(LLFILE* fp)
 {
@@ -583,6 +848,32 @@ void LLViewerInventoryCategory::copyViewerCategory(const LLViewerInventoryCatego
 }
 
 
+void LLViewerInventoryCategory::packUpdateMessage(LLMessageSystem* msg, const LLSD& updates) const
+{
+	for (LLSD::map_const_iterator it = updates.beginMap(); it != updates.endMap(); ++it)
+	{
+		if (it->first != "name")
+		{
+			llerrs << "unhandled field: " << it->first << llendl;
+		}
+	}
+	
+	msg->addUUIDFast(_PREHASH_FolderID, mUUID);
+	msg->addUUIDFast(_PREHASH_ParentID, mParentUUID);
+	S8 type = static_cast<S8>(mPreferredType);
+	msg->addS8Fast(_PREHASH_Type, type);
+	if (updates.has("name"))
+	{
+		std::string new_name = updates["name"].asString();
+		LLInventoryObject::correctInventoryName(new_name);
+		msg->addStringFast(_PREHASH_Name, new_name);
+	}
+	else
+	{
+		msg->addStringFast(_PREHASH_Name, mName);
+	}
+}
+
 void LLViewerInventoryCategory::updateParentOnServer(BOOL restamp) const
 {
 	LLMessageSystem* msg = gMessageSystem;
@@ -1139,180 +1430,87 @@ void move_inventory_item(
 	gAgent.sendReliableMessage();
 }
 
-class AISCommand: public LLHTTPClient::Responder
+// Note this only supports updating an existing item. Goes through AISv3
+// code path where available. Not all uses of item->updateServer() can
+// easily be switched to this paradigm.
+void update_inventory_item(
+	const LLUUID& item_id,
+	const LLSD& updates,
+	LLPointer<LLInventoryCallback> cb)
 {
-public:
-	typedef boost::function<void()> command_func_type;
-
-	AISCommand(LLPointer<LLInventoryCallback> callback):
-		mCallback(callback)
-	{
-		mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 32.0, 2.0, 10);
-	}
-
-	virtual ~AISCommand()
-	{
-	}
-
-	void run_command()
-	{
-		mCommandFunc();
-	}
-
-	void setCommandFunc(command_func_type command_func)
-	{
-		mCommandFunc = command_func;
-	}
-	
-	// Need to do command-specific parsing to get an id here.  May or
-	// may not need to bother, since most LLInventoryCallbacks do
-	// their work in the destructor.
-	virtual bool getResponseUUID(const LLSD& content, LLUUID& id)
-	{
-		return false;
-	}
-	
-	/* virtual */ void 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 item_id; // will default to null if parse fails.
-			getResponseUUID(content,item_id);
-			mCallback->fire(item_id);
-		}
-	}
-
-	/*virtual*/ void httpFailure()
+	LLPointer<LLViewerInventoryItem> obj = gInventory.getItem(item_id);
+	LL_DEBUGS("Inventory") << "item_id: [" << item_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << llendl;
+	if(obj)
 	{
-		const LLSD& content = getContent();
-		S32 status = getStatus();
-		const std::string& reason = getReason();
-		const LLSD& headers = getResponseHeaders();
-		if (!content.isMap())
-		{
-			LL_DEBUGS("Inventory") << "Malformed response contents " << content
-								   << " status " << status << " reason " << reason << llendl;
-		}
-		else
-		{
-			LL_DEBUGS("Inventory") << "failed with content: " << ll_pretty_print_sd(content)
-								   << " status " << status << " reason " << reason << llendl;
-		}
-		mRetryPolicy->onFailure(status, headers);
-		F32 seconds_to_wait;
-		if (mRetryPolicy->shouldRetry(seconds_to_wait))
+		std::string cap;
+		if (AISCommand::getCap(cap))
 		{
-			doAfterInterval(boost::bind(&AISCommand::run_command,this),seconds_to_wait);
+			LLPointer<AISCommand> cmd_ptr = new UpdateItemCommand(item_id, updates, cb);
+			cmd_ptr->run_command();
 		}
-		else
+		else // no cap
 		{
-			// Command func holds a reference to self, need to release it
-			// after a success or final failure.
-			setCommandFunc(no_op);
-		}
-	}
+			LLMessageSystem* msg = gMessageSystem;
+			msg->newMessageFast(_PREHASH_UpdateInventoryItem);
+			msg->nextBlockFast(_PREHASH_AgentData);
+			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+			msg->addUUIDFast(_PREHASH_TransactionID, obj->getTransactionID());
+			msg->nextBlockFast(_PREHASH_InventoryData);
+			msg->addU32Fast(_PREHASH_CallbackID, 0);
+			obj->packUpdateMessage(msg, updates);
+			gAgent.sendReliableMessage();
 
-	static bool getCap(std::string& cap)
-	{
-		if (gAgent.getRegion())
-		{
-			cap = gAgent.getRegion()->getCapability("InventoryAPIv3");
-		}
-		if (!cap.empty())
-		{
-			return true;
+			gInventory.onItemUpdated(item_id, updates);
+			if (cb)
+			{
+				cb->fire(item_id);
+			}
 		}
-		return false;
 	}
+}
 
-private:
-	command_func_type mCommandFunc;
-	LLPointer<LLHTTPRetryPolicy> mRetryPolicy;
-	LLPointer<LLInventoryCallback> mCallback;
-};
-
-class RemoveItemCommand: public AISCommand
+void update_inventory_category(
+	const LLUUID& cat_id,
+	const LLSD& updates,
+	LLPointer<LLInventoryCallback> cb)
 {
-public:
-	RemoveItemCommand(const LLUUID& item_id,
-					  LLPointer<LLInventoryCallback> callback):
-		AISCommand(callback)
+	LLPointer<LLViewerInventoryCategory> obj = gInventory.getCategory(cat_id);
+	LL_DEBUGS("Inventory") << "cat_id: [" << cat_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << llendl;
+	if(obj)
 	{
-		std::string cap;
-		if (!getCap(cap))
+		if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()))
 		{
-			llwarns << "No cap found" << llendl;
+			LLNotificationsUtil::add("CannotModifyProtectedCategories");
 			return;
 		}
-		std::string url = cap + std::string("/item/") + item_id.asString();
-		LL_DEBUGS("Inventory") << "url: " << url << llendl;
-		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);
-	}
-};
 
-class RemoveCategoryCommand: public AISCommand
-{
-public:
-	RemoveCategoryCommand(const LLUUID& item_id,
-						  LLPointer<LLInventoryCallback> callback):
-		AISCommand(callback)
-	{
-		std::string cap;
-		if (!getCap(cap))
+		//std::string cap;
+		// FIXME - restore this once the back-end work has been done.
+		if (0) // if (AISCommand::getCap(cap))
 		{
-			llwarns << "No cap found" << llendl;
-			return;
+			LLPointer<AISCommand> cmd_ptr = new UpdateCategoryCommand(cat_id, updates, cb);
+			cmd_ptr->run_command();
 		}
-		std::string url = cap + std::string("/category/") + item_id.asString();
-		LL_DEBUGS("Inventory") << "url: " << url << llendl;
-		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);
-	}
-};
-
-class PurgeDescendentsCommand: public AISCommand
-{
-public:
-	PurgeDescendentsCommand(const LLUUID& item_id,
-							LLPointer<LLInventoryCallback> callback):
-		AISCommand(callback)
-	{
-		std::string cap;
-		if (!getCap(cap))
+		else // no cap
 		{
-			llwarns << "No cap found" << llendl;
-			return;
+			LLMessageSystem* msg = gMessageSystem;
+			msg->newMessageFast(_PREHASH_UpdateInventoryFolder);
+			msg->nextBlockFast(_PREHASH_AgentData);
+			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+			msg->nextBlockFast(_PREHASH_FolderData);
+			obj->packUpdateMessage(msg, updates);
+			gAgent.sendReliableMessage();
+
+			gInventory.onCategoryUpdated(cat_id, updates);
+			if (cb)
+			{
+				cb->fire(cat_id);
+			}
 		}
-		std::string url = cap + std::string("/category/") + item_id.asString() + "/children";
-		LL_DEBUGS("Inventory") << "url: " << url << llendl;
-		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);
 	}
-};
+}
 
 void remove_inventory_item(
 	const LLUUID& item_id,
diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h
index 4e24dc87d17..c52b0c2d9d0 100755
--- a/indra/newview/llviewerinventory.h
+++ b/indra/newview/llviewerinventory.h
@@ -139,6 +139,8 @@ class LLViewerInventoryItem : public LLInventoryItem, public boost::signals2::tr
 	//void updateAssetOnServer() const;
 
 	virtual void packMessage(LLMessageSystem* msg) const;
+	// Contents of updates will take precedence over fields of item where they differ.
+	void packUpdateMessage(LLMessageSystem* msg, const LLSD& updates) const;
 	virtual void setTransactionID(const LLTransactionID& transaction_id);
 	struct comparePointers
 	{
@@ -224,6 +226,8 @@ class LLViewerInventoryCategory  : public LLInventoryCategory
 	void determineFolderType();
 	void changeType(LLFolderType::EType new_folder_type);
 
+	void packUpdateMessage(LLMessageSystem* msg, const LLSD& updates) const;
+
 private:
 	friend class LLInventoryModel;
 	void localizeName(); // intended to be called from the LLInventoryModel
@@ -363,6 +367,16 @@ void move_inventory_item(
 	const std::string& new_name,
 	LLPointer<LLInventoryCallback> cb);
 
+void update_inventory_item(
+	const LLUUID& item_id,
+	const LLSD& updates,
+	LLPointer<LLInventoryCallback> cb);
+
+void update_inventory_category(
+	const LLUUID& cat_id,
+	const LLSD& updates,
+	LLPointer<LLInventoryCallback> cb);
+
 void remove_inventory_item(
 	const LLUUID& item_id,
 	LLPointer<LLInventoryCallback> cb);
-- 
GitLab