/** * @file llassetuploadresponders.cpp * @brief Processes responses received for asset upload requests. * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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 "llassetuploadresponders.h" // viewer includes #include "llagent.h" #include "llcompilequeue.h" #include "llbuycurrencyhtml.h" #include "llfilepicker.h" #include "llinventorydefines.h" #include "llinventoryobserver.h" #include "llinventorypanel.h" #include "llpermissionsflags.h" #include "llpreviewnotecard.h" #include "llpreviewscript.h" #include "llpreviewgesture.h" #include "llgesturemgr.h" #include "llstatusbar.h" // sendMoneyBalanceRequest() #include "llsdserialize.h" #include "lluploaddialog.h" #include "llviewerobject.h" #include "llviewercontrol.h" #include "llviewerobjectlist.h" #include "llviewermenufile.h" #include "llviewerwindow.h" #include "lltexlayer.h" #include "lltrans.h" // library includes #include "lldir.h" #include "lleconomy.h" #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llnotificationsutil.h" #include "llscrolllistctrl.h" #include "llsdserialize.h" #include "llsdutil.h" #include "llvfs.h" // When uploading multiple files, don't display any of them when uploading more than this number. static const S32 FILE_COUNT_DISPLAY_THRESHOLD = 5; void dialog_refresh_all(); void on_new_single_inventory_upload_complete( LLAssetType::EType asset_type, LLInventoryType::EType inventory_type, const std::string inventory_type_string, const LLUUID& item_folder_id, const std::string& item_name, const std::string& item_description, const LLSD& server_response, S32 upload_price) { bool success = false; if ( upload_price > 0 ) { // this upload costed us L$, update our balance // and display something saying that it cost L$ LLStatusBar::sendMoneyBalanceRequest(); LLSD args; args["AMOUNT"] = llformat("%d", upload_price); LLNotificationsUtil::add("UploadPayment", args); } if( item_folder_id.notNull() ) { U32 everyone_perms = PERM_NONE; U32 group_perms = PERM_NONE; U32 next_owner_perms = PERM_ALL; if( server_response.has("new_next_owner_mask") ) { // The server provided creation perms so use them. // Do not assume we got the perms we asked for in // since the server may not have granted them all. everyone_perms = server_response["new_everyone_mask"].asInteger(); group_perms = server_response["new_group_mask"].asInteger(); next_owner_perms = server_response["new_next_owner_mask"].asInteger(); } else { // The server doesn't provide creation perms // so use old assumption-based perms. if( inventory_type_string != "snapshot") { next_owner_perms = PERM_MOVE | PERM_TRANSFER; } } LLPermissions new_perms; new_perms.init( gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); new_perms.initMasks( PERM_ALL, PERM_ALL, everyone_perms, group_perms, next_owner_perms); U32 inventory_item_flags = 0; if (server_response.has("inventory_flags")) { inventory_item_flags = (U32) server_response["inventory_flags"].asInteger(); if (inventory_item_flags != 0) { llinfos << "inventory_item_flags " << inventory_item_flags << llendl; } } S32 creation_date_now = time_corrected(); LLPointer<LLViewerInventoryItem> item = new LLViewerInventoryItem( server_response["new_inventory_item"].asUUID(), item_folder_id, new_perms, server_response["new_asset"].asUUID(), asset_type, inventory_type, item_name, item_description, LLSaleInfo::DEFAULT, inventory_item_flags, creation_date_now); gInventory.updateItem(item); gInventory.notifyObservers(); success = true; // Show the preview panel for textures and sounds to let // user know that the image (or snapshot) arrived intact. LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(); if ( panel ) { LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); panel->setSelection( server_response["new_inventory_item"].asUUID(), TAKE_FOCUS_NO); // restore keyboard focus gFocusMgr.setKeyboardFocus(focus); } } else { llwarns << "Can't find a folder to put it in" << llendl; } // remove the "Uploading..." message LLUploadDialog::modalUploadFinished(); // Let the Snapshot floater know we have finished uploading a snapshot to inventory. LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); if (asset_type == LLAssetType::AT_TEXTURE && floater_snapshot) { floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); } } LLAssetUploadResponder::LLAssetUploadResponder(const LLSD &post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) : LLHTTPClient::Responder(), mPostData(post_data), mVFileID(vfile_id), mAssetType(asset_type) { if (!gVFS->getExists(vfile_id, asset_type)) { llwarns << "LLAssetUploadResponder called with nonexistant vfile_id" << llendl; mVFileID.setNull(); mAssetType = LLAssetType::AT_NONE; return; } } LLAssetUploadResponder::LLAssetUploadResponder( const LLSD &post_data, const std::string& file_name, LLAssetType::EType asset_type) : LLHTTPClient::Responder(), mPostData(post_data), mFileName(file_name), mAssetType(asset_type) { } LLAssetUploadResponder::~LLAssetUploadResponder() { if (!mFileName.empty()) { // Delete temp file LLFile::remove(mFileName); } } // virtual void LLAssetUploadResponder::error(U32 statusNum, const std::string& reason) { llinfos << "LLAssetUploadResponder::error " << statusNum << " reason: " << reason << llendl; LLSD args; switch(statusNum) { case 400: args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = "Error in upload request. Please visit " "http://secondlife.com/support for help fixing this problem."; LLNotificationsUtil::add("CannotUploadReason", args); break; case 500: default: args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = "The server is experiencing unexpected " "difficulties."; LLNotificationsUtil::add("CannotUploadReason", args); break; } LLUploadDialog::modalUploadFinished(); LLFilePicker::instance().reset(); // unlock file picker when bulk upload fails } //virtual void LLAssetUploadResponder::result(const LLSD& content) { lldebugs << "LLAssetUploadResponder::result from capabilities" << llendl; std::string state = content["state"]; if (state == "upload") { uploadUpload(content); } else if (state == "complete") { // rename file in VFS with new asset id if (mFileName.empty()) { // rename the file in the VFS to the actual asset id // llinfos << "Changing uploaded asset UUID to " << content["new_asset"].asUUID() << llendl; gVFS->renameFile(mVFileID, mAssetType, content["new_asset"].asUUID(), mAssetType); } uploadComplete(content); } else { uploadFailure(content); } } void LLAssetUploadResponder::uploadUpload(const LLSD& content) { std::string uploader = content["uploader"]; if (mFileName.empty()) { LLHTTPClient::postFile(uploader, mVFileID, mAssetType, this); } else { LLHTTPClient::postFile(uploader, mFileName, this); } } void LLAssetUploadResponder::uploadFailure(const LLSD& content) { // remove the "Uploading..." message LLUploadDialog::modalUploadFinished(); LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); if (floater_snapshot) { floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "inventory"))); } std::string reason = content["state"]; // deal with L$ errors if (reason == "insufficient funds") { S32 price = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); LLStringUtil::format_map_t args; args["AMOUNT"] = llformat("%d", price); LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("uploading_costs", args), price ); } else { LLSD args; args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = content["message"].asString(); LLNotificationsUtil::add("CannotUploadReason", args); } } void LLAssetUploadResponder::uploadComplete(const LLSD& content) { } LLNewAgentInventoryResponder::LLNewAgentInventoryResponder( const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, vfile_id, asset_type) { } LLNewAgentInventoryResponder::LLNewAgentInventoryResponder( const LLSD& post_data, const std::string& file_name, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, file_name, asset_type) { } // virtual void LLNewAgentInventoryResponder::error(U32 statusNum, const std::string& reason) { LLAssetUploadResponder::error(statusNum, reason); //LLImportColladaAssetCache::getInstance()->assetUploaded(mVFileID, LLUUID(), FALSE); } //virtual void LLNewAgentInventoryResponder::uploadFailure(const LLSD& content) { LLAssetUploadResponder::uploadFailure(content); //LLImportColladaAssetCache::getInstance()->assetUploaded(mVFileID, content["new_asset"], FALSE); } //virtual void LLNewAgentInventoryResponder::uploadComplete(const LLSD& content) { lldebugs << "LLNewAgentInventoryResponder::result from capabilities" << llendl; //std::ostringstream llsdxml; //LLSDSerialize::toXML(content, llsdxml); //llinfos << "upload complete content:\n " << llsdxml.str() << llendl; LLAssetType::EType asset_type = LLAssetType::lookup(mPostData["asset_type"].asString()); LLInventoryType::EType inventory_type = LLInventoryType::lookup(mPostData["inventory_type"].asString()); S32 expected_upload_cost = 0; // Update L$ and ownership credit information // since it probably changed on the server if (asset_type == LLAssetType::AT_TEXTURE || asset_type == LLAssetType::AT_SOUND || asset_type == LLAssetType::AT_ANIMATION || asset_type == LLAssetType::AT_MESH) { expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); } on_new_single_inventory_upload_complete( asset_type, inventory_type, mPostData["asset_type"].asString(), mPostData["folder_id"].asUUID(), mPostData["name"], mPostData["description"], content, expected_upload_cost); // continue uploading for bulk uploads // *FIX: This is a pretty big hack. What this does is check the // file picker if there are any more pending uploads. If so, // upload that file. std::string next_file = LLFilePicker::instance().getNextFile(); if(!next_file.empty()) { std::string name = gDirUtilp->getBaseFileName(next_file, true); std::string asset_name = name; LLStringUtil::replaceNonstandardASCII( asset_name, '?' ); LLStringUtil::replaceChar(asset_name, '|', '?'); LLStringUtil::stripNonprintable(asset_name); LLStringUtil::trim(asset_name); // Continuing the horrible hack above, we need to extract the originally requested permissions data, if any, // and use them for each next file to be uploaded. Note the requested perms are not the same as the U32 everyone_perms = content.has("new_everyone_mask") ? content["new_everyone_mask"].asInteger() : PERM_NONE; U32 group_perms = content.has("new_group_mask") ? content["new_group_mask"].asInteger() : PERM_NONE; U32 next_owner_perms = content.has("new_next_owner_mask") ? content["new_next_owner_mask"].asInteger() : PERM_NONE; std::string display_name = LLStringUtil::null; LLAssetStorage::LLStoreAssetCallback callback = NULL; void *userdata = NULL; upload_new_resource( next_file, asset_name, asset_name, 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, next_owner_perms, group_perms, everyone_perms, display_name, callback, LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(), userdata); } //LLImportColladaAssetCache::getInstance()->assetUploaded(mVFileID, content["new_asset"], TRUE); } LLSendTexLayerResponder::LLSendTexLayerResponder(const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type, LLBakedUploadData * baked_upload_data) : LLAssetUploadResponder(post_data, vfile_id, asset_type), mBakedUploadData(baked_upload_data) { } LLSendTexLayerResponder::~LLSendTexLayerResponder() { // mBakedUploadData is normally deleted by calls to LLTexLayerSetBuffer::onTextureUploadComplete() below if (mBakedUploadData) { // ...but delete it in the case where uploadComplete() is never called delete mBakedUploadData; mBakedUploadData = NULL; } } // Baked texture upload completed void LLSendTexLayerResponder::uploadComplete(const LLSD& content) { LLUUID item_id = mPostData["item_id"]; std::string result = content["state"]; LLUUID new_id = content["new_asset"]; llinfos << "result: " << result << " new_id: " << new_id << llendl; if (result == "complete" && mBakedUploadData != NULL) { // Invoke LLTexLayerSetBuffer::onTextureUploadComplete(new_id, (void*) mBakedUploadData, 0, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } else { // Invoke the original callback with an error result LLTexLayerSetBuffer::onTextureUploadComplete(new_id, (void*) mBakedUploadData, -1, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } } void LLSendTexLayerResponder::error(U32 statusNum, const std::string& reason) { llinfos << "status: " << statusNum << " reason: " << reason << llendl; // Invoke the original callback with an error result LLTexLayerSetBuffer::onTextureUploadComplete(LLUUID(), (void*) mBakedUploadData, -1, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } LLUpdateAgentInventoryResponder::LLUpdateAgentInventoryResponder( const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, vfile_id, asset_type) { } LLUpdateAgentInventoryResponder::LLUpdateAgentInventoryResponder( const LLSD& post_data, const std::string& file_name, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, file_name, asset_type) { } //virtual void LLUpdateAgentInventoryResponder::uploadComplete(const LLSD& content) { llinfos << "LLUpdateAgentInventoryResponder::result from capabilities" << llendl; LLUUID item_id = mPostData["item_id"]; LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(item_id); if(!item) { llwarns << "Inventory item for " << mVFileID << " is no longer in agent inventory." << llendl; return; } // Update viewer inventory item LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); new_item->setAssetUUID(content["new_asset"].asUUID()); gInventory.updateItem(new_item); gInventory.notifyObservers(); llinfos << "Inventory item " << item->getName() << " saved into " << content["new_asset"].asString() << llendl; LLInventoryType::EType inventory_type = new_item->getInventoryType(); switch(inventory_type) { case LLInventoryType::IT_NOTECARD: { // Update the UI with the new asset. LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", LLSD(item_id)); if(nc) { // *HACK: we have to delete the asset in the VFS so // that the viewer will redownload it. This is only // really necessary if the asset had to be modified by // the uploader, so this can be optimized away in some // cases. A better design is to have a new uuid if the // script actually changed the asset. if(nc->hasEmbeddedInventory()) { gVFS->removeFile(content["new_asset"].asUUID(), LLAssetType::AT_NOTECARD); } nc->refreshFromInventory(new_item->getUUID()); } break; } case LLInventoryType::IT_LSL: { // Find our window and close it if requested. LLPreviewLSL* preview = LLFloaterReg::findTypedInstance<LLPreviewLSL>("preview_script", LLSD(item_id)); if (preview) { // Bytecode save completed if (content["compiled"]) { preview->callbackLSLCompileSucceeded(); } else { preview->callbackLSLCompileFailed(content["errors"]); } } break; } case LLInventoryType::IT_GESTURE: { // If this gesture is active, then we need to update the in-memory // active map with the new pointer. if (LLGestureMgr::instance().isGestureActive(item_id)) { LLUUID asset_id = new_item->getAssetUUID(); LLGestureMgr::instance().replaceGesture(item_id, asset_id); gInventory.notifyObservers(); } //gesture will have a new asset_id LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance<LLPreviewGesture>("preview_gesture", LLSD(item_id)); if(previewp) { previewp->onUpdateSucceeded(); } break; } case LLInventoryType::IT_WEARABLE: default: break; } } LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, vfile_id, asset_type) { } LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_data, const std::string& file_name, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, file_name, asset_type) { } LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_data, const std::string& file_name, const LLUUID& queue_id, LLAssetType::EType asset_type) : LLAssetUploadResponder(post_data, file_name, asset_type), mQueueId(queue_id) { } //virtual void LLUpdateTaskInventoryResponder::uploadComplete(const LLSD& content) { llinfos << "LLUpdateTaskInventoryResponder::result from capabilities" << llendl; LLUUID item_id = mPostData["item_id"]; LLUUID task_id = mPostData["task_id"]; dialog_refresh_all(); switch(mAssetType) { case LLAssetType::AT_NOTECARD: { // Update the UI with the new asset. LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", LLSD(item_id)); if(nc) { // *HACK: we have to delete the asset in the VFS so // that the viewer will redownload it. This is only // really necessary if the asset had to be modified by // the uploader, so this can be optimized away in some // cases. A better design is to have a new uuid if the // script actually changed the asset. if(nc->hasEmbeddedInventory()) { gVFS->removeFile(content["new_asset"].asUUID(), LLAssetType::AT_NOTECARD); } nc->setAssetId(content["new_asset"].asUUID()); nc->refreshFromInventory(); } break; } case LLAssetType::AT_LSL_TEXT: { if(mQueueId.notNull()) { LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", mQueueId); if(NULL != queue) { queue->removeItemByItemID(item_id); } } else { LLLiveLSLEditor* preview = LLFloaterReg::findTypedInstance<LLLiveLSLEditor>("preview_scriptedit", LLSD(item_id)); if (preview) { // Bytecode save completed if (content["compiled"]) { preview->callbackLSLCompileSucceeded(task_id, item_id, mPostData["is_script_running"]); } else { preview->callbackLSLCompileFailed(content["errors"]); } } } break; } default: break; } } ///////////////////////////////////////////////////// // LLNewAgentInventoryVariablePriceResponder::Impl // ///////////////////////////////////////////////////// class LLNewAgentInventoryVariablePriceResponder::Impl { public: Impl( const LLUUID& vfile_id, LLAssetType::EType asset_type, const LLSD& inventory_data) : mVFileID(vfile_id), mAssetType(asset_type), mInventoryData(inventory_data), mFileName("") { if (!gVFS->getExists(vfile_id, asset_type)) { llwarns << "LLAssetUploadResponder called with nonexistant " << "vfile_id " << vfile_id << llendl; mVFileID.setNull(); mAssetType = LLAssetType::AT_NONE; } } Impl( const std::string& file_name, LLAssetType::EType asset_type, const LLSD& inventory_data) : mFileName(file_name), mAssetType(asset_type), mInventoryData(inventory_data) { mVFileID.setNull(); } std::string getFilenameOrIDString() const { return (mFileName.empty() ? mVFileID.asString() : mFileName); } LLUUID getVFileID() const { return mVFileID; } std::string getFilename() const { return mFileName; } LLAssetType::EType getAssetType() const { return mAssetType; } LLInventoryType::EType getInventoryType() const { return LLInventoryType::lookup( mInventoryData["inventory_type"].asString()); } std::string getInventoryTypeString() const { return mInventoryData["inventory_type"].asString(); } LLUUID getFolderID() const { return mInventoryData["folder_id"].asUUID(); } std::string getItemName() const { return mInventoryData["name"].asString(); } std::string getItemDescription() const { return mInventoryData["description"].asString(); } void displayCannotUploadReason(const std::string& reason) { LLSD args; args["FILE"] = getFilenameOrIDString(); args["REASON"] = reason; LLNotificationsUtil::add("CannotUploadReason", args); LLUploadDialog::modalUploadFinished(); } void onApplicationLevelError(const LLSD& error) { static const std::string _IDENTIFIER = "identifier"; static const std::string _INSUFFICIENT_FUNDS = "NewAgentInventory_InsufficientLindenDollarBalance"; static const std::string _MISSING_REQUIRED_PARAMETER = "NewAgentInventory_MissingRequiredParamater"; static const std::string _INVALID_REQUEST_BODY = "NewAgentInventory_InvalidRequestBody"; static const std::string _RESOURCE_COST_DIFFERS = "NewAgentInventory_ResourceCostDiffers"; static const std::string _MISSING_PARAMETER = "missing_parameter"; static const std::string _INVALID_PARAMETER = "invalid_parameter"; static const std::string _MISSING_RESOURCE = "missing_resource"; static const std::string _INVALID_RESOURCE = "invalid_resource"; // TODO* Add the other error_identifiers std::string error_identifier = error[_IDENTIFIER].asString(); // TODO*: Pull these user visible strings from an xml file // to be localized if ( _INSUFFICIENT_FUNDS == error_identifier ) { displayCannotUploadReason("You do not have a sufficient L$ balance to complete this upload."); } else if ( _MISSING_REQUIRED_PARAMETER == error_identifier ) { // Missing parameters if (error.has(_MISSING_PARAMETER) ) { std::string message = "Upload request was missing required parameter '[P]'"; LLStringUtil::replaceString( message, "[P]", error[_MISSING_PARAMETER].asString()); displayCannotUploadReason(message); } else { std::string message = "Upload request was missing a required parameter"; displayCannotUploadReason(message); } } else if ( _INVALID_REQUEST_BODY == error_identifier ) { // Invalid request body, check to see if // a particular parameter was invalid if ( error.has(_INVALID_PARAMETER) ) { std::string message = "Upload parameter '[P]' is invalid."; LLStringUtil::replaceString( message, "[P]", error[_INVALID_PARAMETER].asString()); // See if the server also responds with what resource // is missing. if ( error.has(_MISSING_RESOURCE) ) { message += "\nMissing resource '[R]'."; LLStringUtil::replaceString( message, "[R]", error[_MISSING_RESOURCE].asString()); } else if ( error.has(_INVALID_RESOURCE) ) { message += "\nInvalid resource '[R]'."; LLStringUtil::replaceString( message, "[R]", error[_INVALID_RESOURCE].asString()); } displayCannotUploadReason(message); } else { std::string message = "Upload request was malformed"; displayCannotUploadReason(message); } } else if ( _RESOURCE_COST_DIFFERS == error_identifier ) { displayCannotUploadReason("The resource cost associated with this upload is not consistent with the server."); } else { displayCannotUploadReason("Unknown Error"); } } void onTransportError() { displayCannotUploadReason( "The server is experiencing unexpected difficulties."); } void onTransportError(const LLSD& error) { static const std::string _IDENTIFIER = "identifier"; static const std::string _SERVER_ERROR_AFTER_CHARGE = "NewAgentInventory_ServerErrorAfterCharge"; std::string error_identifier = error[_IDENTIFIER].asString(); // TODO*: Pull the user visible strings from an xml file // to be localized if ( _SERVER_ERROR_AFTER_CHARGE == error_identifier ) { displayCannotUploadReason( "The server is experiencing unexpected difficulties. You may have been charged for the upload."); } else { displayCannotUploadReason( "The server is experiencing unexpected difficulties."); } } bool uploadConfirmationCallback( const LLSD& notification, const LLSD& response, boost::intrusive_ptr<LLNewAgentInventoryVariablePriceResponder> responder) { S32 option; std::string confirmation_url; option = LLNotificationsUtil::getSelectedOption( notification, response); confirmation_url = notification["payload"]["confirmation_url"].asString(); // Yay! We are confirming or cancelling our upload switch(option) { case 0: { confirmUpload(confirmation_url, responder); } break; case 1: default: break; } return false; } void confirmUpload( const std::string& confirmation_url, boost::intrusive_ptr<LLNewAgentInventoryVariablePriceResponder> responder) { if ( getFilename().empty() ) { // we have no filename, use virtual file ID instead LLHTTPClient::postFile( confirmation_url, getVFileID(), getAssetType(), responder); } else { LLHTTPClient::postFile( confirmation_url, getFilename(), responder); } } private: std::string mFileName; LLSD mInventoryData; LLAssetType::EType mAssetType; LLUUID mVFileID; }; /////////////////////////////////////////////// // LLNewAgentInventoryVariablePriceResponder // /////////////////////////////////////////////// LLNewAgentInventoryVariablePriceResponder::LLNewAgentInventoryVariablePriceResponder( const LLUUID& vfile_id, LLAssetType::EType asset_type, const LLSD& inventory_info) { mImpl = new Impl( vfile_id, asset_type, inventory_info); } LLNewAgentInventoryVariablePriceResponder::LLNewAgentInventoryVariablePriceResponder( const std::string& file_name, LLAssetType::EType asset_type, const LLSD& inventory_info) { mImpl = new Impl( file_name, asset_type, inventory_info); } LLNewAgentInventoryVariablePriceResponder::~LLNewAgentInventoryVariablePriceResponder() { delete mImpl; } void LLNewAgentInventoryVariablePriceResponder::errorWithContent( U32 statusNum, const std::string& reason, const LLSD& content) { lldebugs << "LLNewAgentInventoryVariablePrice::error " << statusNum << " reason: " << reason << llendl; if ( content.has("error") ) { static const std::string _ERROR = "error"; mImpl->onTransportError(content[_ERROR]); } else { mImpl->onTransportError(); } } void LLNewAgentInventoryVariablePriceResponder::result(const LLSD& content) { // Parse out application level errors and the appropriate // responses for them static const std::string _ERROR = "error"; static const std::string _STATE = "state"; static const std::string _COMPLETE = "complete"; static const std::string _CONFIRM_UPLOAD = "confirm_upload"; static const std::string _UPLOAD_PRICE = "upload_price"; static const std::string _RESOURCE_COST = "resource_cost"; static const std::string _RSVP = "rsvp"; // Check for application level errors if ( content.has(_ERROR) ) { onApplicationLevelError(content[_ERROR]); return; } std::string state = content[_STATE]; LLAssetType::EType asset_type = mImpl->getAssetType(); if ( _COMPLETE == state ) { // rename file in VFS with new asset id if (mImpl->getFilename().empty()) { // rename the file in the VFS to the actual asset id // llinfos << "Changing uploaded asset UUID to " << content["new_asset"].asUUID() << llendl; gVFS->renameFile( mImpl->getVFileID(), asset_type, content["new_asset"].asUUID(), asset_type); } on_new_single_inventory_upload_complete( asset_type, mImpl->getInventoryType(), mImpl->getInventoryTypeString(), mImpl->getFolderID(), mImpl->getItemName(), mImpl->getItemDescription(), content, content[_UPLOAD_PRICE].asInteger()); // TODO* Add bulk (serial) uploading or add // a super class of this that does so } else if ( _CONFIRM_UPLOAD == state ) { showConfirmationDialog( content[_UPLOAD_PRICE].asInteger(), content[_RESOURCE_COST].asInteger(), content[_RSVP].asString()); } else { onApplicationLevelError(""); } } void LLNewAgentInventoryVariablePriceResponder::onApplicationLevelError( const LLSD& error) { mImpl->onApplicationLevelError(error); } void LLNewAgentInventoryVariablePriceResponder::showConfirmationDialog( S32 upload_price, S32 resource_cost, const std::string& confirmation_url) { if ( 0 == upload_price ) { // don't show confirmation dialog for free uploads, I mean, // they're free! // The creating of a new instrusive_ptr(this) // creates a new boost::intrusive_ptr // which is a copy of this. This code is required because // 'this' is always of type Class* and not the intrusive_ptr, // and thus, a reference to 'this' is not registered // by using just plain 'this'. // Since LLNewAgentInventoryVariablePriceResponder is a // reference counted class, it is possible (since the // reference to a plain 'this' would be missed here) that, // when using plain ol' 'this', that this object // would be deleted before the callback is triggered // and cause sadness. mImpl->confirmUpload( confirmation_url, boost::intrusive_ptr<LLNewAgentInventoryVariablePriceResponder>(this)); } else { LLSD substitutions; LLSD payload; substitutions["PRICE"] = upload_price; payload["confirmation_url"] = confirmation_url; // The creating of a new instrusive_ptr(this) // creates a new boost::intrusive_ptr // which is a copy of this. This code is required because // 'this' is always of type Class* and not the intrusive_ptr, // and thus, a reference to 'this' is not registered // by using just plain 'this'. // Since LLNewAgentInventoryVariablePriceResponder is a // reference counted class, it is possible (since the // reference to a plain 'this' would be missed here) that, // when using plain ol' 'this', that this object // would be deleted before the callback is triggered // and cause sadness. LLNotificationsUtil::add( "UploadCostConfirmation", substitutions, payload, boost::bind( &LLNewAgentInventoryVariablePriceResponder::Impl::uploadConfirmationCallback, mImpl, _1, _2, boost::intrusive_ptr<LLNewAgentInventoryVariablePriceResponder>(this))); } }