phoenix-firestorm/indra/newview/llmarketplacefunctions.cpp

1904 lines
63 KiB
C++

/**
* @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()) <FS:ND> For OpenSim
if (!LLGridManager::getInstance()->isInSLMain())
{
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<LLUUID> sStructureQueue;
static bool sProcessingQueue;
};
std::set<LLUUID> 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<LLUUID>& 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<LLUUID>::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<U32>(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<U32>(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<LLUUID>::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<LLUUID>::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;
}