From 17e1f3692c5c1e9cbc6ba6895b312a8baae9aec2 Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Fri, 5 Apr 2024 19:03:58 -0400 Subject: Port from JsonCPP to Boost.Json for json parsing and serializing (#1054) --- indra/newview/llmarketplacefunctions.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'indra/newview/llmarketplacefunctions.cpp') diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index 8784f403cb..2c98ba3d42 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -42,8 +42,6 @@ #include "llviewermedia.h" #include "llviewernetwork.h" #include "llviewerregion.h" -#include "json/reader.h" // JSON -#include "json/writer.h" // JSON #include "lleventcoro.h" #include "llcoros.h" #include "llcorehttputil.h" -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/newview/llmarketplacefunctions.cpp | 3804 +++++++++++++++--------------- 1 file changed, 1902 insertions(+), 1902 deletions(-) (limited to 'indra/newview/llmarketplacefunctions.cpp') diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index db75bf84bb..7b8211ded8 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -1,1902 +1,1902 @@ -/** - * @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 "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 sStructureQueue; - static bool sProcessingQueue; -}; - -std::set LLMarketplaceInventoryObserver::sStructureQueue; -bool LLMarketplaceInventoryObserver::sProcessingQueue = false; - -void LLMarketplaceInventoryObserver::changed(U32 mask) -{ - 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 && !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; - - 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)) - { - // can trigger notifyObservers - // can cause more structural changes - LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); - } - } - 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() || 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 - if (mMarketPlaceFailureReason.empty()) - { - setSLMStatus(mMarketPlaceStatus); - } - else - { - setSLMConnectionFailure(mMarketPlaceFailureReason); - } - } - 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")); - LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); - } - else if (httpCode == HTTP_SERVICE_UNAVAILABLE) - { - log_SLM_infos("Get /merchant", httpCode, std::string("Merchant is not migrated")); - LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); - } - else - { - 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; - std::string reason = status.toString(); - if (reason.empty()) - { - reason = result["error_code"].asString(); - } - // Since user might not even have a marketplace, there is no reason to report the error - // to the user, instead write it down into listings' floater - LLMarketplaceData::instance().setSLMConnectionFailure(reason); - } - 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); - 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; - mMarketPlaceFailureReason.clear(); - if (mStatusUpdatedSignal) - { - (*mStatusUpdatedSignal)(); - } -} - -void LLMarketplaceData::setSLMConnectionFailure(const std::string& reason) -{ - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; - mMarketPlaceFailureReason = reason; - 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); - 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); - LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); - 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; -} - +/** + * @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 "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 sStructureQueue; + static bool sProcessingQueue; +}; + +std::set LLMarketplaceInventoryObserver::sStructureQueue; +bool LLMarketplaceInventoryObserver::sProcessingQueue = false; + +void LLMarketplaceInventoryObserver::changed(U32 mask) +{ + 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 && !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; + + 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)) + { + // can trigger notifyObservers + // can cause more structural changes + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); + } + } + 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() || 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 + if (mMarketPlaceFailureReason.empty()) + { + setSLMStatus(mMarketPlaceStatus); + } + else + { + setSLMConnectionFailure(mMarketPlaceFailureReason); + } + } + 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")); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); + } + else if (httpCode == HTTP_SERVICE_UNAVAILABLE) + { + log_SLM_infos("Get /merchant", httpCode, std::string("Merchant is not migrated")); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); + } + else + { + 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; + std::string reason = status.toString(); + if (reason.empty()) + { + reason = result["error_code"].asString(); + } + // Since user might not even have a marketplace, there is no reason to report the error + // to the user, instead write it down into listings' floater + LLMarketplaceData::instance().setSLMConnectionFailure(reason); + } + 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); + 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; + mMarketPlaceFailureReason.clear(); + if (mStatusUpdatedSignal) + { + (*mStatusUpdatedSignal)(); + } +} + +void LLMarketplaceData::setSLMConnectionFailure(const std::string& reason) +{ + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; + mMarketPlaceFailureReason = reason; + 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); + 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); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); + 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; +} + -- cgit v1.2.3