diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 2c2d8194e..8e2d9f0b1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -331,6 +331,40 @@ static std::string gLaunchFileOnQuit; // Used on Win32 for other apps to identify our window (eg, win_setup) const char* const VIEWER_WINDOW_CLASSNAME = "Second Life"; // Don't change +//-- LLDeferredTaskList ------------------------------------------------------ + +/** + * A list of deferred tasks. + * + * We sometimes need to defer execution of some code until the viewer gets idle, + * e.g. removing an inventory item from within notifyObservers() may not work out. + * + * Tasks added to this list will be executed in the next LLAppViewer::idle() iteration. + * All tasks are executed only once. + */ +class LLDeferredTaskList: public LLSingleton +{ + LOG_CLASS(LLDeferredTaskList); + + friend class LLAppViewer; + typedef boost::signals2::signal signal_t; + + void addTask(const signal_t::slot_type& cb) + { + mSignal.connect(cb); + } + + void run() + { + if (!mSignal.empty()) + { + mSignal(); + mSignal.disconnect_all_slots(); + } + } + + signal_t mSignal; +}; //---------------------------------------------------------------------------- // File scope definitons const char *VFS_DATA_FILE_BASE = "data.db2.x."; @@ -3348,6 +3382,11 @@ bool LLAppViewer::initCache() } } +void LLAppViewer::addOnIdleCallback(const boost::function& cb) +{ + LLDeferredTaskList::instance().addTask(cb); +} + void LLAppViewer::purgeCache() { LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << llendl; @@ -3932,6 +3971,9 @@ void LLAppViewer::idle() gAudiop->idle(max_audio_decode_time); } } + + // Execute deferred tasks. + LLDeferredTaskList::instance().run(); // Handle shutdown process, for example, // wait for floaters to close, send quit message, diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 92e725268..f9d1b0ed8 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -158,6 +158,7 @@ public: // *NOTE:Mani Fix this for login abstraction!! void handleLoginComplete(); + void addOnIdleCallback(const boost::function& cb); // add a callback to fire (once) when idle void purgeCache(); // Clear the local cache. protected: diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 074dfdc6b..ecafb56b9 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -3548,6 +3548,28 @@ void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) } } + +void LLInventoryModel::removeItem(const LLUUID& item_id) +{ + LLViewerInventoryItem* item = getItem(item_id); + const LLUUID new_parent = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(item && new_parent.notNull() && item->getParentUUID() != new_parent) + { + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(), -1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(new_parent, 1); + update.push_back(new_folder); + accountForUpdate(update); + + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setParent(new_parent); + new_item->updateParentOnServer(TRUE); + updateItem(new_item); + notifyObservers(); + } +} + const LLUUID &LLInventoryModel::getRootFolderID() const { return mRootFolderID; diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 3c9c7c24f..c5b94a0f4 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -343,6 +343,7 @@ public: // consistent internal state. No cache accounting, observer // notification, or server update is performed. void deleteObject(const LLUUID& id); + void removeItem(const LLUUID& item_id); // Delete a particular inventory object by ID, and delete it from // the server. Also updates linked items. diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 34e035f95..df51698f2 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -865,37 +865,13 @@ public: virtual void done() { LL_DEBUGS("Messaging") << "LLDiscardAgentOffer::done()" << LL_ENDL; - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - bool notify = false; - if(trash_id.notNull() && mObjectID.notNull()) - { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(mFolderID, -1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - gInventory.moveObject(mObjectID, trash_id); - LLInventoryObject* obj = gInventory.getObject(mObjectID); - if(obj) - { - // no need to restamp since this is already a freshly - // stamped item. - obj->updateParentOnServer(FALSE); - notify = true; - } - } - else - { - LL_WARNS("Messaging") << "DiscardAgentOffer unable to find: " - << (trash_id.isNull() ? "trash " : "") - << (mObjectID.isNull() ? "object" : "") << LL_ENDL; - } + + // We're invoked from LLInventoryModel::notifyObservers(). + // If we now try to remove the inventory item, it will cause a nested + // notifyObservers() call, which won't work. + // So defer moving the item to trash until viewer gets idle (in a moment). + LLAppViewer::instance()->addOnIdleCallback(boost::bind(&LLInventoryModel::removeItem, &gInventory, mObjectID)); gInventory.removeObserver(this); - if(notify) - { - gInventory.notifyObservers(); - } delete this; } protected: