/** * @file llmarketplacefunctions.cpp * @brief Implementation of assorted functions related to the marketplace * * $LicenseInfo:firstyear=2001&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 "llmarketplacefunctions.h" #include "llagent.h" #include "llbufferstream.h" #include "llcallbacklist.h" #include "llinventoryfunctions.h" #include "llinventoryobserver.h" #include "llnotificationsutil.h" #include "llsdserialize.h" #include "lltimer.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermedia.h" #include "llviewernetwork.h" #include "llviewerregion.h" #include "reader.h" // JSON #include "writer.h" // JSON #include "lleventcoro.h" #include "llcoros.h" #include "llcorehttputil.h" #include "llsdutil.h" // // Helpers // namespace { static std::string getMarketplaceDomain() { std::string domain = "secondlife.com"; if (!LLGridManager::getInstance()->isInProductionGrid()) { const std::string& grid_id = LLGridManager::getInstance()->getGridId(); const std::string& grid_id_lower = utf8str_tolower(grid_id); if (grid_id_lower == "damballah") { domain = "secondlife-staging.com"; } else { domain = llformat("%s.lindenlab.com", grid_id_lower.c_str()); } } return domain; } static std::string getMarketplaceURL(const std::string& urlStringName) { LLStringUtil::format_map_t domain_arg; domain_arg["[MARKETPLACE_DOMAIN_NAME]"] = getMarketplaceDomain(); std::string marketplace_url = LLTrans::getString(urlStringName, domain_arg); return marketplace_url; } // Get the version folder: if there is only one subfolder, we will use it as a version folder LLUUID getVersionFolderIfUnique(const LLUUID& folder_id) { LLUUID version_id = LLUUID::null; LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(folder_id, categories, items); if (categories->size() == 1) { version_id = categories->begin()->get()->getUUID(); } else { LLNotificationsUtil::add("AlertMerchantListingActivateRequired"); } return version_id; } /////////////////////////////////////////////////////////////////////////////// // SLM Reporters void log_SLM_warning(const std::string& request, U32 status, const std::string& reason, const std::string& code, const LLSD& result) { LL_WARNS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", reason : " << reason << ", code : " << code << ", description : " << ll_pretty_print_sd(result) << LL_ENDL; if ((status == 422) && (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT) && result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].isArray() && result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].size() > 4)) { // Unprocessable Entity : Special case that error as it is a frequent answer when trying to list an incomplete listing LLNotificationsUtil::add("MerchantUnprocessableEntity"); } else { // Prompt the user with the warning (so they know why things are failing) LLSD subs; // We do show long descriptions in the alert (unlikely to be readable). The description string will be in the log though. std::string description; if (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT)) { LLSD content = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; if (content.isArray()) { for (LLSD::array_iterator it = content.beginArray(); it != content.endArray(); ++it) { if (!description.empty()) description += "\n"; description += (*it).asString(); } } else { description = content.asString(); } } else { description = result.asString(); } std::string reason_lc = reason; LLStringUtil::toLower(reason_lc); if (!description.empty() && reason_lc.find("unknown") != std::string::npos) { subs["[ERROR_REASON]"] = ""; } else { subs["[ERROR_REASON]"] = "'" + reason +"'\n"; } subs["[ERROR_DESCRIPTION]"] = description; LLNotificationsUtil::add("MerchantTransactionFailed", subs); } } void log_SLM_infos(const std::string& request, U32 status, const std::string& body) { if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) { LL_INFOS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", body or description : " << body << LL_ENDL; } } void log_SLM_infos(const std::string& request, U32 status, const LLSD& body) { log_SLM_infos(request, status, std::string(ll_pretty_print_sd(body))); } } #if 1 namespace LLMarketplaceImport { // Basic interface for this namespace bool hasSessionCookie(); bool inProgress(); bool resultPending(); S32 getResultStatus(); const LLSD& getResults(); bool establishMarketplaceSessionCookie(); bool pollStatus(); bool triggerImport(); // Internal state variables static std::string sMarketplaceCookie = ""; static LLSD sImportId = LLSD::emptyMap(); static bool sImportInProgress = false; static bool sImportPostPending = false; static bool sImportGetPending = false; static S32 sImportResultStatus = 0; static LLSD sImportResults = LLSD::emptyMap(); // Responders void marketplacePostCoro(std::string url) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplacePostCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); httpOpts->setWantHeaders(true); httpOpts->setFollowRedirects(true); httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); httpHeaders->append(HTTP_OUT_HEADER_CONNECTION, "Keep-Alive"); httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML); httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts, httpHeaders); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); S32 httpCode = status.getType(); if ((httpCode == MarketplaceErrorCodes::IMPORT_REDIRECT) || (httpCode == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) || // MAINT-2301 : we determined we can safely ignore that error in that context (httpCode == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT)) { if (gSavedSettings.getBOOL("InventoryOutboxLogging")) { LL_INFOS() << " SLM POST : Ignoring time out status and treating it as success" << LL_ENDL; } httpCode = MarketplaceErrorCodes::IMPORT_DONE; } if (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) { if (gSavedSettings.getBOOL("InventoryOutboxLogging")) { LL_INFOS() << " SLM POST clearing marketplace cookie due to client or server error" << LL_ENDL; } sMarketplaceCookie.clear(); } sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_DONE); sImportPostPending = false; sImportResultStatus = httpCode; { std::stringstream str; LLSDSerialize::toPrettyXML(result, str); LL_INFOS() << "Full results:\n" << str.str() << "\n" << LL_ENDL; } result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); sImportId = result; } void marketplaceGetCoro(std::string url, bool buildHeaders) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplaceGetCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders; LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); httpOpts->setWantHeaders(true); httpOpts->setFollowRedirects(!sMarketplaceCookie.empty()); if (buildHeaders) { httpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); } else { httpHeaders = LLViewerMedia::getInstance()->getHttpHeaders(); } LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts, httpHeaders); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; if (sMarketplaceCookie.empty() && resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) { sMarketplaceCookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString(); } // MAINT-2452 : Do not clear the cookie on IMPORT_DONE_WITH_ERRORS : Happens when trying to import objects with wrong permissions // ACME-1221 : Do not clear the cookie on IMPORT_NOT_FOUND : Happens for newly created Merchant accounts that are initially empty S32 httpCode = status.getType(); if ((httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) && (httpCode != MarketplaceErrorCodes::IMPORT_DONE_WITH_ERRORS) && (httpCode != MarketplaceErrorCodes::IMPORT_NOT_FOUND)) { if (gSavedSettings.getBOOL("InventoryOutboxLogging")) { LL_INFOS() << " SLM GET clearing marketplace cookie due to client or server error" << LL_ENDL; } sMarketplaceCookie.clear(); } else if (gSavedSettings.getBOOL("InventoryOutboxLogging") && (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST)) { LL_INFOS() << " SLM GET : Got error status = " << httpCode << ", but marketplace cookie not cleared." << LL_ENDL; } sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_PROCESSING); sImportGetPending = false; sImportResultStatus = httpCode; result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); sImportResults = result; } // Basic API bool hasSessionCookie() { return !sMarketplaceCookie.empty(); } bool inProgress() { return sImportInProgress; } bool resultPending() { return (sImportPostPending || sImportGetPending); } S32 getResultStatus() { return sImportResultStatus; } const LLSD& getResults() { return sImportResults; } static std::string getInventoryImportURL() { std::string url = getMarketplaceURL("MarketplaceURL"); url += "api/1/"; url += gAgent.getID().getString(); url += "/inventory/import/"; return url; } bool establishMarketplaceSessionCookie() { if (hasSessionCookie()) { return false; } sImportInProgress = true; sImportGetPending = true; std::string url = getInventoryImportURL(); LLCoros::instance().launch("marketplaceGetCoro", boost::bind(&marketplaceGetCoro, url, false)); return true; } bool pollStatus() { if (!hasSessionCookie()) { return false; } sImportGetPending = true; std::string url = getInventoryImportURL(); url += sImportId.asString(); LLCoros::instance().launch("marketplaceGetCoro", boost::bind(&marketplaceGetCoro, url, true)); return true; } bool triggerImport() { if (!hasSessionCookie()) { return false; } sImportId = LLSD::emptyMap(); sImportInProgress = true; sImportPostPending = true; sImportResultStatus = MarketplaceErrorCodes::IMPORT_PROCESSING; sImportResults = LLSD::emptyMap(); std::string url = getInventoryImportURL(); LLCoros::instance().launch("marketplacePostCoro", boost::bind(&marketplacePostCoro, url)); return true; } } #endif // // Interface class // static const F32 MARKET_IMPORTER_UPDATE_FREQUENCY = 1.0f; //static void LLMarketplaceInventoryImporter::update() { if (instanceExists()) { static LLTimer update_timer; if (update_timer.hasExpired()) { LLMarketplaceInventoryImporter::instance().updateImport(); update_timer.setTimerExpirySec(MARKET_IMPORTER_UPDATE_FREQUENCY); } } } LLMarketplaceInventoryImporter::LLMarketplaceInventoryImporter() : mAutoTriggerImport(false) , mImportInProgress(false) , mInitialized(false) , mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) , mErrorInitSignal(NULL) , mStatusChangedSignal(NULL) , mStatusReportSignal(NULL) { } boost::signals2::connection LLMarketplaceInventoryImporter::setInitializationErrorCallback(const status_report_signal_t::slot_type& cb) { if (mErrorInitSignal == NULL) { mErrorInitSignal = new status_report_signal_t(); } return mErrorInitSignal->connect(cb); } boost::signals2::connection LLMarketplaceInventoryImporter::setStatusChangedCallback(const status_changed_signal_t::slot_type& cb) { if (mStatusChangedSignal == NULL) { mStatusChangedSignal = new status_changed_signal_t(); } return mStatusChangedSignal->connect(cb); } boost::signals2::connection LLMarketplaceInventoryImporter::setStatusReportCallback(const status_report_signal_t::slot_type& cb) { if (mStatusReportSignal == NULL) { mStatusReportSignal = new status_report_signal_t(); } return mStatusReportSignal->connect(cb); } void LLMarketplaceInventoryImporter::initialize() { if (mInitialized) { return; } if (!LLMarketplaceImport::hasSessionCookie()) { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; LLMarketplaceImport::establishMarketplaceSessionCookie(); } else { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; } } void LLMarketplaceInventoryImporter::reinitializeAndTriggerImport() { mInitialized = false; mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED; initialize(); mAutoTriggerImport = true; } bool LLMarketplaceInventoryImporter::triggerImport() { const bool import_triggered = LLMarketplaceImport::triggerImport(); if (!import_triggered) { reinitializeAndTriggerImport(); } return import_triggered; } void LLMarketplaceInventoryImporter::updateImport() { const bool in_progress = LLMarketplaceImport::inProgress(); if (in_progress && !LLMarketplaceImport::resultPending()) { const bool polling_status = LLMarketplaceImport::pollStatus(); if (!polling_status) { reinitializeAndTriggerImport(); } } if (mImportInProgress != in_progress) { mImportInProgress = in_progress; // If we are no longer in progress if (!mImportInProgress) { // Look for results success mInitialized = LLMarketplaceImport::hasSessionCookie(); // Report results if (mStatusReportSignal) { (*mStatusReportSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); } if (mInitialized) { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; // Follow up with auto trigger of import if (mAutoTriggerImport) { mAutoTriggerImport = false; mImportInProgress = triggerImport(); } } else { U32 status = LLMarketplaceImport::getResultStatus(); if ((status == MarketplaceErrorCodes::IMPORT_FORBIDDEN) || (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR)) { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT; } else if (status == MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED) { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT; } else { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; } if (mErrorInitSignal && (mMarketPlaceStatus == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)) { (*mErrorInitSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); } } } } // Make sure we trigger the status change with the final state (in case of auto trigger after initialize) if (mStatusChangedSignal) { (*mStatusChangedSignal)(mImportInProgress); } } // // Direct Delivery : Marketplace tuples and data // class LLMarketplaceInventoryObserver : public LLInventoryObserver { public: LLMarketplaceInventoryObserver() {} virtual ~LLMarketplaceInventoryObserver() {} virtual void changed(U32 mask); private: static void onIdleProcessQueue(void *userdata); // doesn't hold just marketplace related ids static std::set sAddQueue; static std::set sStructureQueue; static bool sProcessingQueue; }; std::set LLMarketplaceInventoryObserver::sAddQueue; std::set LLMarketplaceInventoryObserver::sStructureQueue; bool LLMarketplaceInventoryObserver::sProcessingQueue = false; void LLMarketplaceInventoryObserver::changed(U32 mask) { if (mask & LLInventoryObserver::ADD && LLMarketplaceData::instance().hasValidationWaiting()) { // When things are added to the marketplace, we might need to re-validate and fix the containing listings // just add whole list even if it contains items and non-marketplace folders const std::set& changed_items = gInventory.getChangedIDs(); sAddQueue.insert(changed_items.begin(), changed_items.end()); } if (mask & (LLInventoryObserver::INTERNAL | LLInventoryObserver::STRUCTURE)) { // When things are changed in the inventory, this can trigger a host of changes in the marketplace listings folder: // * stock counts changing : no copy items coming in and out will change the stock count on folders // * version and listing folders : moving those might invalidate the marketplace data itself // Since we should cannot raise inventory change while the observer is called (the list will be cleared // once observers are called) we need to raise a flag in the inventory to signal that things have been dirtied. const std::set& changed_items = gInventory.getChangedIDs(); sStructureQueue.insert(changed_items.begin(), changed_items.end()); } if (!sProcessingQueue && (!sAddQueue.empty() || !sStructureQueue.empty())) { gIdleCallbacks.addFunction(onIdleProcessQueue, NULL); // can do without sProcessingQueue, but it's usufull for simplicity and reliability sProcessingQueue = true; } } void LLMarketplaceInventoryObserver::onIdleProcessQueue(void *userdata) { U64 start_time = LLTimer::getTotalTime(); // microseconds const U64 MAX_PROCESSING_TIME = 1000; U64 stop_time = start_time + MAX_PROCESSING_TIME; if (!sAddQueue.empty()) { // Make a copy of sAddQueue since decrementValidationWaiting // can theoretically add more items std::set add_queue(sAddQueue); sAddQueue.clear(); std::set::const_iterator id_it = add_queue.begin(); std::set::const_iterator id_end = add_queue.end(); // First, count the number of items in this list... S32 count = 0; for (; id_it != id_end; ++id_it) { LLInventoryObject* obj = gInventory.getObject(*id_it); if (obj && (LLAssetType::AT_CATEGORY != obj->getType())) { count++; } } // Then, decrement the folders of that amount // Note that of all of those, only one folder will be a listing folder (if at all). // The other will be ignored by the decrement method. id_it = add_queue.begin(); for (; id_it != id_end; ++id_it) { LLInventoryObject* obj = gInventory.getObject(*id_it); if (obj && (LLAssetType::AT_CATEGORY == obj->getType())) { // can trigger notifyObservers LLMarketplaceData::instance().decrementValidationWaiting(obj->getUUID(), count); } } } while (!sStructureQueue.empty() && LLTimer::getTotalTime() < stop_time) { std::set::const_iterator id_it = sStructureQueue.begin(); LLInventoryObject* obj = gInventory.getObject(*id_it); if (obj) { if (LLAssetType::AT_CATEGORY == obj->getType()) { // If it's a folder known to the marketplace, let's check it's in proper shape if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) { LLInventoryCategory* cat = (LLInventoryCategory*)(obj); // can trigger notifyObservers // can cause more structural changes validate_marketplacelistings(cat); } } else { // If it's not a category, it's an item... LLInventoryItem* item = (LLInventoryItem*)(obj); // If it's a no copy item, we may need to update the label count of marketplace listings if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) { LLMarketplaceData::instance().setDirtyCount(); } } } // sStructureQueue could have been modified in validate_marketplacelistings // adding items does not invalidate existing iterator sStructureQueue.erase(id_it); } if (LLApp::isExiting() || (sAddQueue.empty() && sStructureQueue.empty())) { // Nothing to do anymore gIdleCallbacks.deleteFunction(onIdleProcessQueue, NULL); sProcessingQueue = false; } } // Tuple == Item LLMarketplaceTuple::LLMarketplaceTuple() : mListingFolderId(), mListingId(0), mVersionFolderId(), mIsActive(false), mEditURL("") { } LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id) : mListingFolderId(folder_id), mListingId(0), mVersionFolderId(), mIsActive(false), mEditURL("") { } LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed) : mListingFolderId(folder_id), mListingId(listing_id), mVersionFolderId(version_id), mIsActive(is_listed), mEditURL("") { } // Data map LLMarketplaceData::LLMarketplaceData() : mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED), mMarketPlaceDataFetched(MarketplaceFetchCodes::MARKET_FETCH_NOT_DONE), mStatusUpdatedSignal(NULL), mDataFetchedSignal(NULL), mDirtyCount(false) { mInventoryObserver = new LLMarketplaceInventoryObserver; gInventory.addObserver(mInventoryObserver); } LLMarketplaceData::~LLMarketplaceData() { gInventory.removeObserver(mInventoryObserver); } LLSD LLMarketplaceData::getMarketplaceStringSubstitutions() { std::string marketplace_url = getMarketplaceURL("MarketplaceURL"); std::string marketplace_url_create = getMarketplaceURL("MarketplaceURL_CreateStore"); std::string marketplace_url_dashboard = getMarketplaceURL("MarketplaceURL_Dashboard"); std::string marketplace_url_imports = getMarketplaceURL("MarketplaceURL_Imports"); std::string marketplace_url_info = getMarketplaceURL("MarketplaceURL_LearnMore"); LLSD marketplace_sub_map; marketplace_sub_map["[MARKETPLACE_URL]"] = marketplace_url; marketplace_sub_map["[MARKETPLACE_CREATE_STORE_URL]"] = marketplace_url_create; marketplace_sub_map["[MARKETPLACE_LEARN_MORE_URL]"] = marketplace_url_info; marketplace_sub_map["[MARKETPLACE_DASHBOARD_URL]"] = marketplace_url_dashboard; marketplace_sub_map["[MARKETPLACE_IMPORTS_URL]"] = marketplace_url_imports; return marketplace_sub_map; } void LLMarketplaceData::initializeSLM(const status_updated_signal_t::slot_type& cb) { if (mStatusUpdatedSignal == NULL) { mStatusUpdatedSignal = new status_updated_signal_t(); } mStatusUpdatedSignal->connect(cb); if (mMarketPlaceStatus != MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) { // If already initialized, just confirm the status so the callback gets called setSLMStatus(mMarketPlaceStatus); } else { mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; LLCoros::instance().launch("getMerchantStatus", boost::bind(&LLMarketplaceData::getMerchantStatusCoro, this)); } } void LLMarketplaceData::getMerchantStatusCoro() { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); httpOpts->setFollowRedirects(true); std::string url = getSLMConnectURL("/merchant"); if (url.empty()) { LL_WARNS("Marketplace") << "No marketplace capability on Sim" << LL_ENDL; setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); return; } LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { S32 httpCode = status.getType(); if (httpCode == HTTP_NOT_FOUND) { log_SLM_infos("Get /merchant", httpCode, std::string("User is not a merchant")); setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); } else if (httpCode == HTTP_SERVICE_UNAVAILABLE) { log_SLM_infos("Get /merchant", httpCode, std::string("Merchant is not migrated")); setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); } else if (httpCode == HTTP_INTERNAL_ERROR) { // 499 includes timeout and ssl error - marketplace is down or having issues, we do not show it in this request according to MAINT-5938 LL_WARNS("SLM") << "SLM Merchant Request failed with status: " << httpCode << ", reason : " << status.toString() << ", code : " << result["error_code"].asString() << ", description : " << result["error_description"].asString() << LL_ENDL; LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); } else { std::string err_code = result["error_code"].asString(); //std::string err_description = result["error_description"].asString(); log_SLM_warning("Get /merchant", httpCode, status.toString(), err_code, result["error_description"]); setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); } return; } log_SLM_infos("Get /merchant", status.getType(), std::string("User is a merchant")); setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT); } void LLMarketplaceData::setDataFetchedSignal(const status_updated_signal_t::slot_type& cb) { if (mDataFetchedSignal == NULL) { mDataFetchedSignal = new status_updated_signal_t(); } mDataFetchedSignal->connect(cb); } // Get/Post/Put requests to the SLM Server using the SLM API void LLMarketplaceData::getSLMListings() { const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); setUpdating(marketplaceFolderId, true); LLCoros::instance().launch("getSLMListings", boost::bind(&LLMarketplaceData::getSLMListingsCoro, this, marketplaceFolderId)); } void LLMarketplaceData::getSLMListingsCoro(LLUUID folderId) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); std::string url = getSLMConnectURL("/listings"); LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); setUpdating(folderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { log_SLM_warning("Get /listings", status.getType(), status.toString(), "", result); setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_FAILED); update_marketplace_category(folderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Get /listings", static_cast(status.getType()), result); // Extract the info from the results for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int listingId = listing["id"].asInteger(); bool isListed = listing["is_listed"].asBoolean(); std::string editUrl = listing["edit_url"].asString(); LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); int count = listing["inventory_info"]["count_on_hand"].asInteger(); if (folderUuid.notNull()) { addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); } } // Update all folders under the root setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_DONE); update_marketplace_category(folderId, false); gInventory.notifyObservers(); } void LLMarketplaceData::getSLMListing(S32 listingId) { LLUUID folderId = getListingFolder(listingId); setUpdating(folderId, true); LLCoros::instance().launch("getSingleListingCoro", boost::bind(&LLMarketplaceData::getSingleListingCoro, this, listingId, folderId)); } void LLMarketplaceData::getSingleListingCoro(S32 listingId, LLUUID folderId) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); setUpdating(folderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { if (status.getType() == HTTP_NOT_FOUND) { // That listing does not exist -> delete its record from the local SLM data store deleteListing(folderId, false); } else { log_SLM_warning("Get /listing", status.getType(), status.toString(), "", result); } update_marketplace_category(folderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Get /listings", static_cast(status.getType()), result); // Extract the info from the results for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int resListingId = listing["id"].asInteger(); bool isListed = listing["is_listed"].asBoolean(); std::string editUrl = listing["edit_url"].asString(); LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); int count = listing["inventory_info"]["count_on_hand"].asInteger(); // Update that listing setListingID(folderUuid, resListingId, false); setVersionFolderID(folderUuid, versionUuid, false); setActivationState(folderUuid, isListed, false); setListingURL(folderUuid, editUrl, false); setCountOnHand(folderUuid, count, false); update_marketplace_category(folderUuid, false); gInventory.notifyObservers(); } } void LLMarketplaceData::createSLMListing(const LLUUID& folder_id, const LLUUID& version_id, S32 count) { setUpdating(folder_id, true); LLCoros::instance().launch("createSLMListingCoro", boost::bind(&LLMarketplaceData::createSLMListingCoro, this, folder_id, version_id, count)); } void LLMarketplaceData::createSLMListingCoro(LLUUID folderId, LLUUID versionId, S32 count) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); LLViewerInventoryCategory* category = gInventory.getCategory(folderId); LLSD invInfo; invInfo["listing_folder_id"] = folderId; invInfo["version_folder_id"] = versionId; invInfo["count_on_hand"] = count; LLSD listing; listing["name"] = category->getName(); listing["inventory_info"] = invInfo; LLSD postData; postData["listing"] = listing; std::string url = getSLMConnectURL("/listings"); LLSD result = httpAdapter->postJsonAndSuspend(httpRequest, url, postData, httpHeaders); setUpdating(folderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { log_SLM_warning("Post /listings", status.getType(), status.toString(), "", result); update_marketplace_category(folderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Post /listings", status.getType(), result); if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) { LL_INFOS("SLM") << "Received an empty response for folder " << folderId << LL_ENDL; return; } // Extract the info from the results for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int listingId = listing["id"].asInteger(); bool isListed = listing["is_listed"].asBoolean(); std::string editUrl = listing["edit_url"].asString(); LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); int count = listing["inventory_info"]["count_on_hand"].asInteger(); addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); update_marketplace_category(folderUuid, false); gInventory.notifyObservers(); } } void LLMarketplaceData::updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count) { setUpdating(folder_id, true); LLCoros::instance().launch("updateSLMListingCoro", boost::bind(&LLMarketplaceData::updateSLMListingCoro, this, folder_id, listing_id, version_id, is_listed, count)); } void LLMarketplaceData::updateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, bool isListed, S32 count) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); LLSD invInfo; invInfo["listing_folder_id"] = folderId; invInfo["version_folder_id"] = versionId; invInfo["count_on_hand"] = count; LLSD listing; listing["inventory_info"] = invInfo; listing["id"] = listingId; listing["is_listed"] = isListed; LLSD postData; postData["listing"] = listing; std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); setUpdating(folderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { log_SLM_warning("Put /listing", status.getType(), status.toString(), "", result); update_marketplace_category(folderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Put /listing", status.getType(), result); if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) { LL_INFOS("SLM") << "Received an empty response for listing " << listingId << " folder " << folderId << LL_ENDL; // Try to get listing more directly after a delay const float FORCE_UPDATE_TIMEOUT = 5.0; llcoro::suspendUntilTimeout(FORCE_UPDATE_TIMEOUT); if (!LLApp::isExiting() && LLMarketplaceData::instanceExists()) { getSLMListing(listingId); } return; } // Extract the info from the Json string for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int listing_id = listing["id"].asInteger(); bool is_listed = listing["is_listed"].asBoolean(); std::string edit_url = listing["edit_url"].asString(); LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); int onHand = listing["inventory_info"]["count_on_hand"].asInteger(); // Update that listing setListingID(folderUuid, listing_id, false); setVersionFolderID(folderUuid, versionUuid, false); setActivationState(folderUuid, is_listed, false); setListingURL(folderUuid, edit_url, false); setCountOnHand(folderUuid, onHand, false); update_marketplace_category(folderUuid, false); gInventory.notifyObservers(); // Show a notification alert if what we got is not what we expected // (this actually doesn't result in an error status from the SLM API protocol) if ((isListed != is_listed) || (versionId != versionUuid)) { LLSD subs; subs["[URL]"] = edit_url; LLNotificationsUtil::add("AlertMerchantListingNotUpdated", subs); } } } void LLMarketplaceData::associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, const LLUUID& source_folder_id) { setUpdating(folder_id, true); setUpdating(source_folder_id, true); LLCoros::instance().launch("associateSLMListingCoro", boost::bind(&LLMarketplaceData::associateSLMListingCoro, this, folder_id, listing_id, version_id, source_folder_id)); } void LLMarketplaceData::associateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, LLUUID sourceFolderId) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); LLSD invInfo; invInfo["listing_folder_id"] = folderId; invInfo["version_folder_id"] = versionId; LLSD listing; listing["id"] = listingId; listing["inventory_info"] = invInfo; LLSD postData; postData["listing"] = listing; // Send request std::string url = getSLMConnectURL("/associate_inventory/") + llformat("%d", listingId); LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); setUpdating(folderId, false); setUpdating(sourceFolderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { log_SLM_warning("Put /associate_inventory", status.getType(), status.toString(), "", result); update_marketplace_category(folderId, false); update_marketplace_category(sourceFolderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Put /associate_inventory", status.getType(), result); for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int listing_id = listing["id"].asInteger(); bool is_listed = listing["is_listed"].asBoolean(); std::string edit_url = listing["edit_url"].asString(); LLUUID folder_uuid = listing["inventory_info"]["listing_folder_id"].asUUID(); LLUUID version_uuid = listing["inventory_info"]["version_folder_id"].asUUID(); int count = listing["inventory_info"]["count_on_hand"].asInteger(); // Check that the listing ID is not already associated to some other record LLUUID old_listing = LLMarketplaceData::instance().getListingFolder(listing_id); if (old_listing.notNull()) { // If it is already used, unlist the old record (we can't have 2 listings with the same listing ID) deleteListing(old_listing); } // Add the new association addListing(folder_uuid, listing_id, version_uuid, is_listed, edit_url, count); update_marketplace_category(folder_uuid, false); gInventory.notifyObservers(); // The stock count needs to be updated with the new local count now updateCountOnHand(folder_uuid, 1); } // Always update the source folder so its widget updates update_marketplace_category(sourceFolderId, false); } void LLMarketplaceData::deleteSLMListing(S32 listingId) { LLCoros::instance().launch("deleteSLMListingCoro", boost::bind(&LLMarketplaceData::deleteSLMListingCoro, this, listingId)); } void LLMarketplaceData::deleteSLMListingCoro(S32 listingId) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); httpHeaders->append("Accept", "application/json"); httpHeaders->append("Content-Type", "application/json"); std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); LLUUID folderId = getListingFolder(listingId); setUpdating(folderId, true); LLSD result = httpAdapter->deleteJsonAndSuspend(httpRequest, url, httpHeaders); setUpdating(folderId, false); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); if (!status) { log_SLM_warning("Delete /listing", status.getType(), status.toString(), "", result); update_marketplace_category(folderId, false); gInventory.notifyObservers(); return; } log_SLM_infos("Delete /listing", status.getType(), result); for (LLSD::array_iterator it = result["listings"].beginArray(); it != result["listings"].endArray(); ++it) { LLSD listing = *it; int listing_id = listing["id"].asInteger(); LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); deleteListing(folder_id); } } std::string LLMarketplaceData::getSLMConnectURL(const std::string& route) { std::string url; LLViewerRegion *regionp = gAgent.getRegion(); if (regionp) { // Get DirectDelivery cap url = regionp->getCapability("DirectDelivery"); if (!url.empty()) { url += route; } } return url; } void LLMarketplaceData::setSLMStatus(U32 status) { mMarketPlaceStatus = status; if (mStatusUpdatedSignal) { (*mStatusUpdatedSignal)(); } } void LLMarketplaceData::setSLMDataFetched(U32 status) { mMarketPlaceDataFetched = status; if (mDataFetchedSignal) { (*mDataFetchedSignal)(); } } bool LLMarketplaceData::isSLMDataFetched() { return mMarketPlaceDataFetched == MarketplaceFetchCodes::MARKET_FETCH_DONE; } // Creation / Deletion / Update // Methods publicly called bool LLMarketplaceData::createListing(const LLUUID& folder_id) { if (isListed(folder_id)) { // Listing already exists -> exit with error return false; } // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately S32 count = -1; LLUUID version_id = getVersionFolderIfUnique(folder_id); if (version_id.notNull()) { count = compute_stock_count(version_id, true); } // Validate the count on hand if (count == COMPUTE_STOCK_NOT_EVALUATED) { // If the count on hand cannot be evaluated, we will consider it empty (out of stock) at creation time // It will get reevaluated and updated once the items are fetched count = 0; } // Post the listing creation request to SLM createSLMListing(folder_id, version_id, count); return true; } bool LLMarketplaceData::clearListing(const LLUUID& folder_id, S32 depth) { if (folder_id.isNull()) { // Folder doesn't exists -> exit with error return false; } // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } // Folder id can be the root of the listing or not so we need to retrieve the root first LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); S32 listing_id = getListingID(listing_uuid); if (listing_id == 0) { // Listing doesn't exists -> exit with error return false; } // Update the SLM Server so that this listing is deleted (actually, archived...) deleteSLMListing(listing_id); return true; } bool LLMarketplaceData::getListing(const LLUUID& folder_id, S32 depth) { if (folder_id.isNull()) { // Folder doesn't exists -> exit with error return false; } // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } // Folder id can be the root of the listing or not so we need to retrieve the root first LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); S32 listing_id = getListingID(listing_uuid); if (listing_id == 0) { // Listing doesn't exists -> exit with error return false; } // Get listing data from SLM getSLMListing(listing_id); return true; } bool LLMarketplaceData::getListing(S32 listing_id) { if (listing_id == 0) { return false; } // Get listing data from SLM getSLMListing(listing_id); return true; } bool LLMarketplaceData::activateListing(const LLUUID& folder_id, bool activate, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } // Folder id can be the root of the listing or not so we need to retrieve the root first LLUUID listing_uuid = nested_parent_id(folder_id, depth); S32 listing_id = getListingID(listing_uuid); if (listing_id == 0) { // Listing doesn't exists -> exit with error return false; } if (getActivationState(listing_uuid) == activate) { // If activation state is unchanged, no point spamming SLM with an update return true; } LLUUID version_uuid = getVersionFolder(listing_uuid); // Also update the count on hand S32 count = compute_stock_count(folder_id); if (count == COMPUTE_STOCK_NOT_EVALUATED) { // If the count on hand cannot be evaluated locally, we should not change that SLM value // We are assuming that this issue is local and should not modify server side values count = getCountOnHand(listing_uuid); } // Post the listing update request to SLM updateSLMListing(listing_uuid, listing_id, version_uuid, activate, count); return true; } bool LLMarketplaceData::setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } // Folder id can be the root of the listing or not so we need to retrieve the root first LLUUID listing_uuid = nested_parent_id(folder_id, depth); S32 listing_id = getListingID(listing_uuid); if (listing_id == 0) { // Listing doesn't exists -> exit with error return false; } if (getVersionFolder(listing_uuid) == version_id) { // If version folder is unchanged, no point spamming SLM with an update return true; } // Note: if the version_id is cleared, we need to unlist the listing, otherwise, state unchanged bool is_listed = (version_id.isNull() ? false : getActivationState(listing_uuid)); // Also update the count on hand S32 count = compute_stock_count(version_id); if (count == COMPUTE_STOCK_NOT_EVALUATED) { // If the count on hand cannot be evaluated, we will consider it empty (out of stock) when resetting the version folder // It will get reevaluated and updated once the items are fetched count = 0; } // Post the listing update request to SLM updateSLMListing(listing_uuid, listing_id, version_id, is_listed, count); return true; } bool LLMarketplaceData::updateCountOnHand(const LLUUID& folder_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } // Folder id can be the root of the listing or not so we need to retrieve the root first LLUUID listing_uuid = nested_parent_id(folder_id, depth); S32 listing_id = getListingID(listing_uuid); if (listing_id == 0) { // Listing doesn't exists -> exit with error return false; } // Compute the new count on hand S32 count = compute_stock_count(folder_id); if (count == getCountOnHand(listing_uuid)) { // If count on hand is unchanged, no point spamming SLM with an update return true; } else if (count == COMPUTE_STOCK_NOT_EVALUATED) { // If local count on hand is not known at that point, do *not* force an update to SLM return false; } // Get the unchanged values bool is_listed = getActivationState(listing_uuid); LLUUID version_uuid = getVersionFolder(listing_uuid); // Post the listing update request to SLM updateSLMListing(listing_uuid, listing_id, version_uuid, is_listed, count); // Force the local value as it prevents spamming (count update may occur in burst when restocking) // Note that if SLM has a good reason to return a different value, it'll be updated by the responder setCountOnHand(listing_uuid, count, false); return true; } bool LLMarketplaceData::associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id) { if (isListed(folder_id)) { // Listing already exists -> exit with error return false; } // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately LLUUID version_id = getVersionFolderIfUnique(folder_id); // Post the listing associate request to SLM associateSLMListing(folder_id, listing_id, version_id, source_folder_id); return true; } // Methods privately called or called by SLM responders to perform changes bool LLMarketplaceData::addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, const std::string& edit_url, S32 count) { mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id, listing_id, version_id, is_listed); mMarketplaceItems[folder_id].mEditURL = edit_url; mMarketplaceItems[folder_id].mCountOnHand = count; if (version_id.notNull()) { mVersionFolders[version_id] = folder_id; } return true; } bool LLMarketplaceData::deleteListing(const LLUUID& folder_id, bool update) { LLUUID version_folder = getVersionFolder(folder_id); if (mMarketplaceItems.erase(folder_id) != 1) { return false; } mVersionFolders.erase(version_folder); if (update) { update_marketplace_category(folder_id, false); gInventory.notifyObservers(); } return true; } bool LLMarketplaceData::deleteListing(S32 listing_id, bool update) { if (listing_id == 0) { return false; } LLUUID folder_id = getListingFolder(listing_id); return deleteListing(folder_id, update); } // Accessors bool LLMarketplaceData::getActivationState(const LLUUID& folder_id) { // Listing folder case marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it != mMarketplaceItems.end()) { return (it->second).mIsActive; } // Version folder case version_folders_list_t::iterator it_version = mVersionFolders.find(folder_id); if (it_version != mVersionFolders.end()) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(it_version->second); if (it != mMarketplaceItems.end()) { return (it->second).mIsActive; } } return false; } S32 LLMarketplaceData::getListingID(const LLUUID& folder_id) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); return (it == mMarketplaceItems.end() ? 0 : (it->second).mListingId); } S32 LLMarketplaceData::getCountOnHand(const LLUUID& folder_id) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); return (it == mMarketplaceItems.end() ? -1 : (it->second).mCountOnHand); } LLUUID LLMarketplaceData::getVersionFolder(const LLUUID& folder_id) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); return (it == mMarketplaceItems.end() ? LLUUID::null : (it->second).mVersionFolderId); } // Reverse lookup : find the listing folder id from the listing id LLUUID LLMarketplaceData::getListingFolder(S32 listing_id) { marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); while (it != mMarketplaceItems.end()) { if ((it->second).mListingId == listing_id) { return (it->second).mListingFolderId; } it++; } return LLUUID::null; } std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } LLUUID listing_uuid = nested_parent_id(folder_id, depth); marketplace_items_list_t::iterator it = mMarketplaceItems.find(listing_uuid); return (it == mMarketplaceItems.end() ? "" : (it->second).mEditURL); } bool LLMarketplaceData::isListed(const LLUUID& folder_id) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); return (it != mMarketplaceItems.end()); } bool LLMarketplaceData::isListedAndActive(const LLUUID& folder_id) { return (isListed(folder_id) && getActivationState(folder_id)); } bool LLMarketplaceData::isVersionFolder(const LLUUID& folder_id) { version_folders_list_t::iterator it = mVersionFolders.find(folder_id); return (it != mVersionFolders.end()); } bool LLMarketplaceData::isInActiveFolder(const LLUUID& obj_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(obj_id); } LLUUID listing_uuid = nested_parent_id(obj_id, depth); bool active = getActivationState(listing_uuid); LLUUID version_uuid = getVersionFolder(listing_uuid); return (active && ((obj_id == version_uuid) || gInventory.isObjectDescendentOf(obj_id, version_uuid))); } LLUUID LLMarketplaceData::getActiveFolder(const LLUUID& obj_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(obj_id); } LLUUID listing_uuid = nested_parent_id(obj_id, depth); return (getActivationState(listing_uuid) ? getVersionFolder(listing_uuid) : LLUUID::null); } bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth) { // Evaluate the depth if it wasn't passed as a parameter if (depth < 0) { depth = depth_nesting_in_marketplace(folder_id); } if ((depth <= 0) || (depth > 2)) { // Only listing and version folders though are concerned by that status return false; } else { const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); std::set::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); if (it != mPendingUpdateSet.end()) { // If we're waiting for data for the marketplace listings root, we are in the updating process for all return true; } else { // Check if the listing folder is waiting or data LLUUID listing_uuid = nested_parent_id(folder_id, depth); it = mPendingUpdateSet.find(listing_uuid); return (it != mPendingUpdateSet.end()); } } } void LLMarketplaceData::setUpdating(const LLUUID& folder_id, bool isUpdating) { std::set::iterator it = mPendingUpdateSet.find(folder_id); if (it != mPendingUpdateSet.end()) { mPendingUpdateSet.erase(it); } if (isUpdating) { mPendingUpdateSet.insert(folder_id); } } void LLMarketplaceData::setValidationWaiting(const LLUUID& folder_id, S32 count) { mValidationWaitingList[folder_id] = count; } void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 count) { waiting_list_t::iterator found = mValidationWaitingList.find(folder_id); if (found != mValidationWaitingList.end()) { found->second -= count; if (found->second <= 0) { mValidationWaitingList.erase(found); LLInventoryCategory *cat = gInventory.getCategory(folder_id); validate_marketplacelistings(cat); update_marketplace_category(folder_id); gInventory.notifyObservers(); } } } // Private Modifiers bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id, bool update) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it == mMarketplaceItems.end()) { return false; } (it->second).mListingId = listing_id; if (update) { update_marketplace_category(folder_id, false); gInventory.notifyObservers(); } return true; } bool LLMarketplaceData::setCountOnHand(const LLUUID& folder_id, S32 count, bool update) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it == mMarketplaceItems.end()) { return false; } (it->second).mCountOnHand = count; return true; } bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it == mMarketplaceItems.end()) { return false; } LLUUID old_version_id = (it->second).mVersionFolderId; if (old_version_id == version_id) { return false; } (it->second).mVersionFolderId = version_id; mVersionFolders.erase(old_version_id); if (version_id.notNull()) { mVersionFolders[version_id] = folder_id; } if (update) { update_marketplace_category(old_version_id, false); update_marketplace_category(version_id, false); gInventory.notifyObservers(); } return true; } bool LLMarketplaceData::setActivationState(const LLUUID& folder_id, bool activate, bool update) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it == mMarketplaceItems.end()) { return false; } (it->second).mIsActive = activate; if (update) { update_marketplace_category((it->second).mListingFolderId, false); gInventory.notifyObservers(); } return true; } bool LLMarketplaceData::setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update) { marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); if (it == mMarketplaceItems.end()) { return false; } (it->second).mEditURL = edit_url; return true; }