A query url will no longer be built in making the request The status of marketplace will no longer get reset to uninitialized upon region change. Merchant status will no longer be requested on regions without the DD cap
2024 lines
58 KiB
C++
2024 lines
58 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 "llhttpclient.h"
|
|
#include "llinventoryfunctions.h"
|
|
#include "llinventoryobserver.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llsdjson.h"
|
|
#include "lltimer.h"
|
|
#include "lltrans.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llviewerinventory.h"
|
|
#include "llviewermedia.h"
|
|
#include "llviewernetwork.h"
|
|
#include "llviewerregion.h"
|
|
#include "hippogridmanager.h"
|
|
|
|
#include "llsdutil.h"
|
|
|
|
//
|
|
// Helpers
|
|
//
|
|
|
|
std::string getLoginUriDomain()
|
|
{
|
|
LLURI uri(gHippoGridManager->getConnectedGrid()->getLoginUri());
|
|
std::string hostname = uri.hostName(); // Ie, "login.<gridid>.lindenlab.com"
|
|
if (hostname.substr(0, 6) == "login.")
|
|
{
|
|
hostname = hostname.substr(6); // "<gridid>.lindenlab.com"
|
|
}
|
|
return hostname;
|
|
}
|
|
|
|
// Apart from well-known cases, in general this function returns the domain of the loginUri (with the "login." stripped off).
|
|
// This should be correct for all SL BETA grids, assuming they have the form of "login.<gridId>.lindenlab.com", in which
|
|
// case it returns "<gridId>.lindenlab.com".
|
|
//
|
|
// Well-known cases that deviate from this:
|
|
// agni --> "secondlife.com"
|
|
// damballah --> "secondlife-staging.com"
|
|
//
|
|
static std::string getMarketplaceDomain()
|
|
{
|
|
std::string domain;
|
|
if (gHippoGridManager->getCurrentGrid()->isSecondLife())
|
|
{
|
|
if (gHippoGridManager->getConnectedGrid()->isInProductionGrid())
|
|
{
|
|
domain = "secondlife.com"; // agni
|
|
}
|
|
else
|
|
{
|
|
// SecondLife(tm) BETA grid.
|
|
// Using the login URI is a bit of a kludge, but it's the best we've got at the moment.
|
|
domain = utf8str_tolower(getLoginUriDomain()); // <gridid>.lindenlab.com; ie, "aditi.lindenlab.com".
|
|
llassert(domain.length() > 14 && domain.substr(domain.length() - 14) == ".lindenlab.com");
|
|
if (domain == "damballah.lindenlab.com")
|
|
{
|
|
domain = "secondlife-staging.com";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: Find out if OpenSim, and Avination adopted any outbox stuffs, if so code HippoGridManager for this
|
|
// WhiteCore has not.
|
|
// For now, set domain on other grids to the loginUri domain, so we don't harass LL web services.
|
|
domain = getLoginUriDomain(); //gHippoGridManager->getCurrentGrid()->getMarketPlaceDomain();
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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& content)
|
|
{
|
|
LL_WARNS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", reason : " << reason << ", code : " << code << ", description : " << ll_pretty_print_sd(content) << LL_ENDL;
|
|
if ((status == 422) &&
|
|
content.isArray() &&
|
|
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 (content.isArray())
|
|
{
|
|
for (auto it = content.beginArray(); it != content.endArray(); ++it)
|
|
{
|
|
if (!description.empty())
|
|
description += '\n';
|
|
description += (*it).asString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
description = content.asString();
|
|
}
|
|
}
|
|
std::string reason_lc = reason;
|
|
LLStringUtil::toLower(reason_lc);
|
|
if (!description.empty() && reason_lc.find("unknown") != std::string::npos)
|
|
{
|
|
subs["[ERROR_REASON]"] = LLStringUtil::null;
|
|
}
|
|
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, const std::string& url, const std::string& body)
|
|
{
|
|
if (gSavedSettings.getBOOL("MarketplaceListingsLogging"))
|
|
{
|
|
LL_INFOS("SLM") << "SLM API : Sending " << request << ". url : " << url << ", body : " << body << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
class LLSLMGetMerchantResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMGetMerchantResponder);
|
|
public:
|
|
|
|
LLSLMGetMerchantResponder() {}
|
|
|
|
protected:
|
|
void completedRaw(const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) override
|
|
{
|
|
S32 httpCode = getStatus();
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
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 if (httpCode == 499)
|
|
{
|
|
const auto& content(getContent());
|
|
// 499 includes timeout and ssl error - marketplace is down or having issues, we do not show it in this request according to MAINT-5938
|
|
LL_WARNS("SLM") << "SLM Merchant Request failed with status: " << httpCode
|
|
<< ", reason : " << getReason()
|
|
<< ", code : " << content.get("error_code")
|
|
<< ", description : " << content.get("error_description") << LL_ENDL;
|
|
LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
log_SLM_warning("Get /merchant", httpCode, getReason(), LLStringUtil::null, getContent());
|
|
LLMarketplaceData::instance().
|
|
setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Get /merchant", getStatus(), "User is a merchant");
|
|
LLMarketplaceData::instance().
|
|
setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT);
|
|
}
|
|
virtual char const* getName() const { return "getMerchantStatus"; }
|
|
};
|
|
|
|
class LLSLMGetListingsResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMGetListingsResponder);
|
|
public:
|
|
LLSLMGetListingsResponder(const LLUUID& folder_id)
|
|
{
|
|
folderId = folder_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().
|
|
setUpdating(folderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);;
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
log_SLM_warning("Get /listings", getStatus(), getReason(), LLStringUtil::null, result);
|
|
LLMarketplaceData::instance().
|
|
setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_FAILED);
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Get /listings", getStatus(), body);
|
|
|
|
// 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())
|
|
{
|
|
LLMarketplaceData::instance().
|
|
addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count);
|
|
}
|
|
}
|
|
|
|
// Update all folders under the root
|
|
LLMarketplaceData::instance().
|
|
setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_DONE);
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
virtual char const* getName() const { return "getSLMListings"; }
|
|
private:
|
|
LLUUID folderId;
|
|
};
|
|
|
|
class LLSLMCreateListingsResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMCreateListingsResponder);
|
|
public:
|
|
|
|
LLSLMCreateListingsResponder(const LLUUID& folder_id)
|
|
{
|
|
folderId = folder_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().setUpdating(folderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);;
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
log_SLM_warning("Post /listings", getStatus(), getReason(), LLStringUtil::null, result);
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Post /listings", getStatus(), body);
|
|
|
|
// Extract the info from the Json string
|
|
auto it = result["listings"].beginArray();
|
|
|
|
while (it != result["listings"].endArray())
|
|
{
|
|
auto listing = *it;
|
|
|
|
int listing_id = listing["id"].asInteger();
|
|
bool is_listed = listing["is_listed"].asBoolean();
|
|
std::string edit_url = listing["edit_url"].asString();
|
|
std::string folder_uuid_string = listing["inventory_info"]["listing_folder_id"].asString();
|
|
std::string version_uuid_string = listing["inventory_info"]["version_folder_id"].asString();
|
|
int count = listing["inventory_info"]["count_on_hand"].asInteger();
|
|
|
|
LLUUID folder_id(folder_uuid_string);
|
|
LLUUID version_id(version_uuid_string);
|
|
LLMarketplaceData::instance().addListing(folder_id,listing_id,version_id,is_listed,edit_url,count);
|
|
update_marketplace_category(folder_id, false);
|
|
gInventory.notifyObservers();
|
|
it++;
|
|
}
|
|
}
|
|
virtual char const* getName() const { return "LLSLMCreateListingsResponder"; }
|
|
private:
|
|
LLUUID folderId;
|
|
};
|
|
|
|
class LLSLMGetListingResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMGetListingResponder);
|
|
public:
|
|
|
|
LLSLMGetListingResponder(const LLUUID& folder_id)
|
|
{
|
|
folderId = folder_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().
|
|
setUpdating(folderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
if (getStatus() == HTTP_NOT_FOUND)
|
|
{
|
|
// That listing does not exist -> delete its record from the local SLM data store
|
|
LLMarketplaceData::instance().
|
|
deleteListing(folderId, false);
|
|
}
|
|
else
|
|
{
|
|
log_SLM_warning("Get /listing", getStatus(), getReason(), LLStringUtil::null, result);
|
|
}
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Get /listing", getStatus(), body);
|
|
|
|
// 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
|
|
LLMarketplaceData::instance().
|
|
setListingID(folderUuid, resListingId, false);
|
|
LLMarketplaceData::instance().
|
|
setVersionFolderID(folderUuid, versionUuid, false);
|
|
LLMarketplaceData::instance().
|
|
setActivationState(folderUuid, isListed, false);
|
|
LLMarketplaceData::instance().
|
|
setListingURL(folderUuid, editUrl, false);
|
|
LLMarketplaceData::instance().
|
|
setCountOnHand(folderUuid, count, false);
|
|
update_marketplace_category(folderUuid, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
}
|
|
virtual char const* getName() const { return "getSingleListingCoro"; }
|
|
private:
|
|
LLUUID folderId;
|
|
};
|
|
|
|
class LLSLMUpdateListingsResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMUpdateListingsResponder);
|
|
public:
|
|
|
|
LLSLMUpdateListingsResponder(const LLUUID& folder_id, bool expected_listed_state, const LLUUID& expected_version_id)
|
|
{
|
|
folderId = folder_id;
|
|
isListed = expected_listed_state;
|
|
versionId = expected_version_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().
|
|
setUpdating(folderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
log_SLM_warning("Put /listing", getStatus(), getReason(), LLStringUtil::null, result);
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Put /listing", getStatus(), body);
|
|
|
|
// 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
|
|
LLMarketplaceData::instance().
|
|
setListingID(folderUuid, listing_id, false);
|
|
LLMarketplaceData::instance().
|
|
setVersionFolderID(folderUuid, versionUuid, false);
|
|
LLMarketplaceData::instance().
|
|
setActivationState(folderUuid, is_listed, false);
|
|
LLMarketplaceData::instance().
|
|
setListingURL(folderUuid, edit_url, false);
|
|
LLMarketplaceData::instance().
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
virtual char const* getName() const { return "updateSLMListingCoro"; }
|
|
private:
|
|
LLUUID folderId;
|
|
bool isListed;
|
|
LLUUID versionId;
|
|
};
|
|
|
|
class LLSLMAssociateListingsResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMAssociateListingsResponder);
|
|
public:
|
|
|
|
LLSLMAssociateListingsResponder(const LLUUID& folder_id, const LLUUID& source_folder_id)
|
|
{
|
|
folderId = folder_id;
|
|
sourceFolderId = source_folder_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().setUpdating(folderId, false);
|
|
LLMarketplaceData::instance().setUpdating(sourceFolderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
log_SLM_warning("Put /associate_inventory", getStatus(), getReason(), LLStringUtil::null, result);
|
|
update_marketplace_category(folderId, false);
|
|
update_marketplace_category(sourceFolderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Put /associate_inventory", getStatus(), body);
|
|
|
|
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)
|
|
LLMarketplaceData::instance().deleteListing(old_listing);
|
|
}
|
|
|
|
// Add the new association
|
|
LLMarketplaceData::instance().
|
|
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
|
|
LLMarketplaceData::instance().
|
|
updateCountOnHand(folder_uuid, 1);
|
|
}
|
|
|
|
// Always update the source folder so its widget updates
|
|
update_marketplace_category(sourceFolderId, false);
|
|
}
|
|
|
|
virtual char const* getName() const { return "associateSLMListingCoro"; }
|
|
private:
|
|
LLUUID folderId; // This is the folder now associated with the id.
|
|
LLUUID sourceFolderId; // This is the folder initially associated with the id. Can be LLUUID::null
|
|
};
|
|
|
|
class LLSLMDeleteListingsResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLSLMDeleteListingsResponder);
|
|
public:
|
|
|
|
LLSLMDeleteListingsResponder(const LLUUID& folder_id)
|
|
{
|
|
folderId = folder_id;
|
|
}
|
|
|
|
virtual void completedRaw(const LLChannelDescriptors& channels,
|
|
const LLIOPipe::buffer_ptr_t& buffer)
|
|
{
|
|
LLMarketplaceData::instance().setUpdating(folderId, false);
|
|
|
|
std::string body;
|
|
decode_raw_body(channels, buffer, body);
|
|
auto json = nlohmann::json::parse(body);
|
|
LLSD result = LlsdFromJson(json);
|
|
|
|
if (!isGoodStatus(mStatus))
|
|
{
|
|
log_SLM_warning("Delete /listing", getStatus(), getReason(), LLStringUtil::null, result);
|
|
update_marketplace_category(folderId, false);
|
|
gInventory.notifyObservers();
|
|
return;
|
|
}
|
|
|
|
log_SLM_infos("Delete /listing", getStatus(), body);
|
|
|
|
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);
|
|
LLMarketplaceData::instance().deleteListing(folder_id);
|
|
}
|
|
}
|
|
virtual char const* getName() const { return "deleteSLMListingCoro"; }
|
|
private:
|
|
LLUUID folderId;
|
|
};
|
|
|
|
// SLM Responders End
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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();
|
|
|
|
static LLTimer slmGetTimer;
|
|
static LLTimer slmPostTimer;
|
|
|
|
// Responders
|
|
|
|
class LLImportPostResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLImportPostResponder);
|
|
public:
|
|
void httpCompleted() override
|
|
{
|
|
slmPostTimer.stop();
|
|
|
|
if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
|
|
{
|
|
LL_INFOS() << " SLM [timer:" << slmPostTimer.getElapsedTimeF32() << "] "
|
|
<< dumpResponse() << LL_ENDL;
|
|
}
|
|
|
|
S32 httpCode = getStatus();
|
|
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;
|
|
|
|
sImportId = getContent();
|
|
}
|
|
|
|
char const* getName() const override { return "LLImportPostResponder"; }
|
|
};
|
|
|
|
class LLImportGetResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LOG_CLASS(LLImportGetResponder);
|
|
public:
|
|
/*virtual*/ bool needsHeaders(void) const { return true; }
|
|
|
|
/*virtual*/ void completedHeaders(void)
|
|
{
|
|
if (mStatus == HTTP_OK)
|
|
{
|
|
std::string value = get_cookie("_slm_session");
|
|
if (!value.empty())
|
|
{
|
|
sMarketplaceCookie = value;
|
|
}
|
|
else if (sMarketplaceCookie.empty())
|
|
{
|
|
LL_WARNS() << "No such cookie \"_slm_session\" received!" << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*virtual*/ void httpCompleted()
|
|
{
|
|
slmGetTimer.stop();
|
|
|
|
if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
|
|
{
|
|
LL_INFOS() << " SLM [timer:" << slmGetTimer.getElapsedTimeF32() << "] "
|
|
<< dumpResponse() << LL_ENDL;
|
|
}
|
|
|
|
// 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 = getStatus();
|
|
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;
|
|
|
|
sImportResults = getContent();
|
|
}
|
|
|
|
char const* getName() const override { return "LLImportGetResponder"; }
|
|
};
|
|
|
|
// 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();
|
|
|
|
AIHTTPHeaders headers = LLViewerMedia::getHeaders();
|
|
if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
|
|
{
|
|
LL_INFOS() << " SLM GET: establishMarketplaceSessionCookie, LLHTTPClient::get, url = " << url << LL_ENDL;
|
|
std::stringstream str;
|
|
str << headers; //LLSDSerialize::toPrettyXML(headers, str);
|
|
LL_INFOS() << " SLM GET: headers " << LL_ENDL;
|
|
LL_INFOS() << str.str() << LL_ENDL;
|
|
}
|
|
|
|
slmGetTimer.start();
|
|
LLHTTPClient::get(url, new LLImportGetResponder(), headers);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool pollStatus()
|
|
{
|
|
if (!hasSessionCookie())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sImportGetPending = true;
|
|
|
|
std::string url = getInventoryImportURL();
|
|
|
|
url += sImportId.asString();
|
|
|
|
// Make the headers for the post
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "*/*");
|
|
headers.addHeader("Cookie", sMarketplaceCookie);
|
|
// *TODO: Why are we setting Content-Type for a GET request?
|
|
headers.addHeader("Content-Type", "application/llsd+xml");
|
|
headers.addHeader("User-Agent", LLViewerMedia::getCurrentUserAgent());
|
|
|
|
if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
|
|
{
|
|
LL_INFOS() << " SLM GET: pollStatus, LLHTTPClient::get, url = " << url << LL_ENDL;
|
|
std::stringstream str;
|
|
str << headers; //LLSDSerialize::toPrettyXML(headers, str);
|
|
LL_INFOS() << " SLM GET: headers " << LL_ENDL;
|
|
LL_INFOS() << str.str() << LL_ENDL;
|
|
}
|
|
|
|
slmGetTimer.start();
|
|
LLHTTPClient::get(url, new LLImportGetResponder(), headers);
|
|
|
|
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();
|
|
|
|
// Make the headers for the post
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "*/*");
|
|
headers.addHeader("Connection", "Keep-Alive");
|
|
headers.addHeader("Cookie", sMarketplaceCookie);
|
|
headers.addHeader("Content-Type", "application/xml");
|
|
headers.addHeader("User-Agent", LLViewerMedia::getCurrentUserAgent());
|
|
|
|
if (gSavedSettings.getBOOL("InventoryOutboxLogging"))
|
|
{
|
|
LL_INFOS() << " SLM POST: triggerImport, LLHTTPClient::post, url = " << url << LL_ENDL;
|
|
std::stringstream str;
|
|
str << headers; //LLSDSerialize::toPrettyXML(headers, str);
|
|
LL_INFOS() << " SLM POST: headers " << LL_ENDL;
|
|
LL_INFOS() << str.str() << LL_ENDL;
|
|
}
|
|
|
|
slmPostTimer.start();
|
|
LLHTTPClient::post(url, LLSD(), new LLImportPostResponder(), headers);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// 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);
|
|
};
|
|
|
|
void LLMarketplaceInventoryObserver::changed(U32 mask)
|
|
{
|
|
// When things are added to the marketplace, we might need to re-validate and fix the containing listings
|
|
if (mask & LLInventoryObserver::ADD)
|
|
{
|
|
const uuid_set_t& changed_items = gInventory.getChangedIDs();
|
|
|
|
auto id_it = changed_items.begin();
|
|
auto id_end = changed_items.end();
|
|
// First, count the number of items in this list...
|
|
S32 count = 0;
|
|
for (;id_it != id_end; ++id_it)
|
|
{
|
|
LLInventoryObject* obj = gInventory.getObject(*id_it);
|
|
if (obj && (LLAssetType::AT_CATEGORY != obj->getType()))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
// Then, decrement the folders of that amount
|
|
// Note that of all of those, only one folder will be a listing folder (if at all).
|
|
// The other will be ignored by the decrement method.
|
|
id_it = changed_items.begin();
|
|
for (;id_it != id_end; ++id_it)
|
|
{
|
|
LLInventoryObject* obj = gInventory.getObject(*id_it);
|
|
if (obj && (LLAssetType::AT_CATEGORY == obj->getType()))
|
|
{
|
|
LLMarketplaceData::instance().decrementValidationWaiting(obj->getUUID(),count);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
|
|
if (mask & (LLInventoryObserver::INTERNAL | LLInventoryObserver::STRUCTURE))
|
|
{
|
|
const uuid_set_t& changed_items = gInventory.getChangedIDs();
|
|
|
|
auto id_it = changed_items.begin();
|
|
auto id_end = changed_items.end();
|
|
for (;id_it != id_end; ++id_it)
|
|
{
|
|
LLInventoryObject* obj = gInventory.getObject(*id_it);
|
|
if (obj)
|
|
{
|
|
if (LLAssetType::AT_CATEGORY == obj->getType())
|
|
{
|
|
// If it's a folder known to the marketplace, let's check it's in proper shape
|
|
if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it))
|
|
{
|
|
LLInventoryCategory* cat = (LLInventoryCategory*)(obj);
|
|
validate_marketplacelistings(cat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If it's not a category, it's an item...
|
|
LLInventoryItem* item = (LLInventoryItem*)(obj);
|
|
// If it's a no copy item, we may need to update the label count of marketplace listings
|
|
if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()))
|
|
{
|
|
LLMarketplaceData::instance().setDirtyCount();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
void LLMarketplaceData::initializeSLM(const status_updated_signal_t::slot_type& cb)
|
|
{
|
|
if (mStatusUpdatedSignal == NULL)
|
|
{
|
|
mStatusUpdatedSignal = new status_updated_signal_t();
|
|
}
|
|
mStatusUpdatedSignal->connect(cb);
|
|
|
|
if (mMarketPlaceStatus != MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED)
|
|
{
|
|
// If already initialized, just confirm the status so the callback gets called
|
|
setSLMStatus(mMarketPlaceStatus);
|
|
}
|
|
else
|
|
{
|
|
mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
log_SLM_infos("LLHTTPClient::get", url, LLStringUtil::null);
|
|
LLHTTPClient::get(url, new LLSLMGetMerchantResponder);
|
|
}
|
|
|
|
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()
|
|
{
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("Content-Type", "application/json");
|
|
|
|
// Send request
|
|
std::string url = getSLMConnectURL("/listings");
|
|
log_SLM_infos("LLHTTPClient::get", url, "");
|
|
const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
|
|
setUpdating(marketplacelistings_id, true);
|
|
LLHTTPClient::get(url, new LLSLMGetListingsResponder(marketplacelistings_id), headers);
|
|
}
|
|
|
|
void LLMarketplaceData::getSLMListing(S32 listingId)
|
|
{
|
|
LLUUID folderId = getListingFolder(listingId);
|
|
setUpdating(folderId, true);
|
|
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("Content-Type", "application/json");
|
|
|
|
// Send request
|
|
std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId);
|
|
log_SLM_infos("LLHTTPClient::get", url, LLStringUtil::null);
|
|
LLHTTPClient::get(url, new LLSLMGetListingResponder(folderId), headers);
|
|
}
|
|
|
|
void LLMarketplaceData::createSLMListing(const LLUUID& folderId, const LLUUID& versionId, S32 count)
|
|
{
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("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;
|
|
|
|
auto json = LlsdToJson(postData);
|
|
auto json_str = json.dump();
|
|
|
|
// postRaw() takes ownership of the buffer and releases it later.
|
|
size_t size = json_str.length();
|
|
U8* data = new U8[size];
|
|
memcpy(data, (U8*)(json_str.c_str()), size);
|
|
|
|
// Send request
|
|
std::string url = getSLMConnectURL("/listings");
|
|
log_SLM_infos("LLHTTPClient::postRaw", url, json_str);
|
|
setUpdating(folderId, true);
|
|
LLHTTPClient::postRaw(url, data, size, new LLSLMCreateListingsResponder(folderId), headers);
|
|
}
|
|
|
|
void LLMarketplaceData::updateSLMListing(const LLUUID& folderId, S32 listingId, const LLUUID& versionId, bool isListed, S32 count)
|
|
{
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("Content-Type", "application/json");
|
|
|
|
// Note : auto unlist if the count is 0 (out of stock)
|
|
if (isListed && (count == 0))
|
|
{
|
|
isListed = false;
|
|
LLNotificationsUtil::add("AlertMerchantStockFolderEmpty");
|
|
}
|
|
|
|
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;
|
|
|
|
auto json = LlsdToJson(postData);
|
|
auto json_str = json.dump();
|
|
|
|
// postRaw() takes ownership of the buffer and releases it later.
|
|
size_t size = json_str.size();
|
|
U8* data = new U8[size];
|
|
memcpy(data, (U8*)(json_str.c_str()), size);
|
|
|
|
std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId);
|
|
log_SLM_infos("LLHTTPClient::putRaw", url, json_str);
|
|
setUpdating(folderId, true);
|
|
LLHTTPClient::putRaw(url, data, size, new LLSLMUpdateListingsResponder(folderId, isListed, versionId), headers);
|
|
}
|
|
|
|
void LLMarketplaceData::associateSLMListing(const LLUUID& folderId, S32 listingId, const LLUUID& versionId, const LLUUID& sourceFolderId)
|
|
{
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("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;
|
|
|
|
auto json = LlsdToJson(postData);
|
|
auto json_str = json.dump();
|
|
|
|
// postRaw() takes ownership of the buffer and releases it later.
|
|
size_t size = json_str.size();
|
|
U8* data = new U8[size];
|
|
memcpy(data, (U8*)(json_str.c_str()), size);
|
|
|
|
// Send request
|
|
std::string url = getSLMConnectURL("/associate_inventory/") + llformat("%d", listingId);
|
|
log_SLM_infos("LLHTTPClient::putRaw", url, json_str);
|
|
setUpdating(folderId, true);
|
|
setUpdating(sourceFolderId, true);
|
|
|
|
LLHTTPClient::putRaw(url, data, size, new LLSLMAssociateListingsResponder(folderId, sourceFolderId), headers);
|
|
}
|
|
|
|
void LLMarketplaceData::deleteSLMListing(S32 listingId)
|
|
{
|
|
AIHTTPHeaders headers;
|
|
headers.addHeader("Accept", "application/json");
|
|
headers.addHeader("Content-Type", "application/json");
|
|
|
|
// Send request
|
|
std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId);
|
|
log_SLM_infos("LLHTTPClient::del", url, "");
|
|
LLUUID folderId = getListingFolder(listingId);
|
|
|
|
setUpdating(folderId, true);
|
|
LLHTTPClient::del(url, new LLSLMDeleteListingsResponder(folderId), headers);
|
|
}
|
|
|
|
std::string LLMarketplaceData::getSLMConnectURL(const std::string& route)
|
|
{
|
|
std::string url;
|
|
LLViewerRegion* regionp = gAgent.getRegion();
|
|
if (regionp)
|
|
{
|
|
// Get DirectDelivery cap
|
|
url = regionp->getCapability("DirectDelivery");
|
|
if (!url.empty())
|
|
{
|
|
url += route;
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
|
|
void LLMarketplaceData::setSLMStatus(U32 status)
|
|
{
|
|
mMarketPlaceStatus = status;
|
|
if (mStatusUpdatedSignal)
|
|
{
|
|
(*mStatusUpdatedSignal)();
|
|
}
|
|
}
|
|
|
|
void LLMarketplaceData::setSLMDataFetched(U32 status)
|
|
{
|
|
mMarketPlaceDataFetched = status;
|
|
if (mDataFetchedSignal)
|
|
{
|
|
(*mDataFetchedSignal)();
|
|
}
|
|
}
|
|
|
|
// 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 exist -> 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 exist -> 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 exist -> 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 exist -> 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 exist -> 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 exist -> 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 exist -> exit with error
|
|
return false;
|
|
}
|
|
|
|
// Compute the new count on hand
|
|
S32 count = compute_stock_count(folder_id);
|
|
|
|
if (count == getCountOnHand(listing_uuid))
|
|
{
|
|
// If count on hand is unchanged, no point spamming SLM with an update
|
|
return true;
|
|
}
|
|
else if (count == COMPUTE_STOCK_NOT_EVALUATED)
|
|
{
|
|
// If local count on hand is not known at that point, do *not* force an update to SLM
|
|
return false;
|
|
}
|
|
|
|
// Get the unchanged values
|
|
bool is_listed = getActivationState(listing_uuid);
|
|
LLUUID version_uuid = getVersionFolder(listing_uuid);
|
|
|
|
|
|
// Post the listing update request to SLM
|
|
updateSLMListing(listing_uuid, listing_id, version_uuid, is_listed, count);
|
|
|
|
// Force the local value as it prevents spamming (count update may occur in burst when restocking)
|
|
// Note that if SLM has a good reason to return a different value, it'll be updated by the responder
|
|
setCountOnHand(listing_uuid, count, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id)
|
|
{
|
|
if (isListed(folder_id))
|
|
{
|
|
// Listing already exists -> exit with error
|
|
return false;
|
|
}
|
|
|
|
// Get the version folder: if there is only one subfolder, we will set it as a version folder immediately
|
|
LLUUID version_id = getVersionFolderIfUnique(folder_id);
|
|
|
|
// Post the listing associate request to SLM
|
|
associateSLMListing(folder_id, listing_id, version_id, source_folder_id);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Methods privately called or called by SLM responders to perform changes
|
|
bool LLMarketplaceData::addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, const std::string& edit_url, S32 count)
|
|
{
|
|
mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id, listing_id, version_id, is_listed);
|
|
mMarketplaceItems[folder_id].mEditURL = edit_url;
|
|
mMarketplaceItems[folder_id].mCountOnHand = count;
|
|
if (version_id.notNull())
|
|
{
|
|
mVersionFolders[version_id] = folder_id;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::deleteListing(const LLUUID& folder_id, bool update)
|
|
{
|
|
LLUUID version_folder = getVersionFolder(folder_id);
|
|
|
|
if (mMarketplaceItems.erase(folder_id) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
mVersionFolders.erase(version_folder);
|
|
|
|
if (update)
|
|
{
|
|
update_marketplace_category(folder_id, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::deleteListing(S32 listing_id, bool update)
|
|
{
|
|
if (listing_id == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LLUUID folder_id = getListingFolder(listing_id);
|
|
return deleteListing(folder_id, update);
|
|
}
|
|
|
|
// Accessors
|
|
bool LLMarketplaceData::getActivationState(const LLUUID& folder_id)
|
|
{
|
|
// Listing folder case
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it != mMarketplaceItems.end())
|
|
{
|
|
return (it->second).mIsActive;
|
|
}
|
|
// Version folder case
|
|
version_folders_list_t::iterator it_version = mVersionFolders.find(folder_id);
|
|
if (it_version != mVersionFolders.end())
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(it_version->second);
|
|
if (it != mMarketplaceItems.end())
|
|
{
|
|
return (it->second).mIsActive;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
S32 LLMarketplaceData::getListingID(const LLUUID& folder_id)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
return (it == mMarketplaceItems.end() ? 0 : (it->second).mListingId);
|
|
}
|
|
|
|
S32 LLMarketplaceData::getCountOnHand(const LLUUID& folder_id)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
return (it == mMarketplaceItems.end() ? -1 : (it->second).mCountOnHand);
|
|
}
|
|
|
|
LLUUID LLMarketplaceData::getVersionFolder(const LLUUID& folder_id)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
return (it == mMarketplaceItems.end() ? LLUUID::null : (it->second).mVersionFolderId);
|
|
}
|
|
|
|
// Reverse lookup : find the listing folder id from the listing id
|
|
LLUUID LLMarketplaceData::getListingFolder(S32 listing_id)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.begin();
|
|
while (it != mMarketplaceItems.end())
|
|
{
|
|
if ((it->second).mListingId == listing_id)
|
|
{
|
|
return (it->second).mListingFolderId;
|
|
}
|
|
it++;
|
|
}
|
|
return LLUUID::null;
|
|
}
|
|
|
|
std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id, S32 depth)
|
|
{
|
|
// Evaluate the depth if it wasn't passed as a parameter
|
|
if (depth < 0)
|
|
{
|
|
depth = depth_nesting_in_marketplace(folder_id);
|
|
|
|
}
|
|
|
|
LLUUID listing_uuid = nested_parent_id(folder_id, depth);
|
|
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(listing_uuid);
|
|
return (it == mMarketplaceItems.end() ? "" : (it->second).mEditURL);
|
|
}
|
|
|
|
bool LLMarketplaceData::isListed(const LLUUID& folder_id)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
return (it != mMarketplaceItems.end());
|
|
}
|
|
|
|
bool LLMarketplaceData::isListedAndActive(const LLUUID& folder_id)
|
|
{
|
|
return (isListed(folder_id) && getActivationState(folder_id));
|
|
}
|
|
|
|
bool LLMarketplaceData::isVersionFolder(const LLUUID& folder_id)
|
|
{
|
|
version_folders_list_t::iterator it = mVersionFolders.find(folder_id);
|
|
return (it != mVersionFolders.end());
|
|
}
|
|
|
|
bool LLMarketplaceData::isInActiveFolder(const LLUUID& obj_id, S32 depth)
|
|
{
|
|
// Evaluate the depth if it wasn't passed as a parameter
|
|
if (depth < 0)
|
|
{
|
|
depth = depth_nesting_in_marketplace(obj_id);
|
|
|
|
}
|
|
|
|
LLUUID listing_uuid = nested_parent_id(obj_id, depth);
|
|
bool active = getActivationState(listing_uuid);
|
|
LLUUID version_uuid = getVersionFolder(listing_uuid);
|
|
return (active && ((obj_id == version_uuid) || gInventory.isObjectDescendentOf(obj_id, version_uuid)));
|
|
}
|
|
|
|
LLUUID LLMarketplaceData::getActiveFolder(const LLUUID& obj_id, S32 depth)
|
|
{
|
|
// Evaluate the depth if it wasn't passed as a parameter
|
|
if (depth < 0)
|
|
{
|
|
depth = depth_nesting_in_marketplace(obj_id);
|
|
|
|
}
|
|
|
|
LLUUID listing_uuid = nested_parent_id(obj_id, depth);
|
|
return (getActivationState(listing_uuid) ? getVersionFolder(listing_uuid) : LLUUID::null);
|
|
}
|
|
|
|
bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth)
|
|
{
|
|
// Evaluate the depth if it wasn't passed as a parameter
|
|
if (depth < 0)
|
|
{
|
|
depth = depth_nesting_in_marketplace(folder_id);
|
|
}
|
|
if ((depth <= 0) || (depth > 2))
|
|
{
|
|
// Only listing and version folders though are concerned by that status
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
|
|
auto 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 for 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)
|
|
{
|
|
auto it = mPendingUpdateSet.find(folder_id);
|
|
if (it != mPendingUpdateSet.end())
|
|
{
|
|
mPendingUpdateSet.erase(it);
|
|
}
|
|
if (isUpdating)
|
|
{
|
|
mPendingUpdateSet.insert(folder_id);
|
|
}
|
|
}
|
|
|
|
void LLMarketplaceData::setValidationWaiting(const LLUUID& folder_id, S32 count)
|
|
{
|
|
mValidationWaitingList[folder_id] = count;
|
|
}
|
|
|
|
void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 count)
|
|
{
|
|
waiting_list_t::iterator found = mValidationWaitingList.find(folder_id);
|
|
if (found != mValidationWaitingList.end())
|
|
{
|
|
found->second -= count;
|
|
if (found->second <= 0)
|
|
{
|
|
mValidationWaitingList.erase(found);
|
|
LLInventoryCategory *cat = gInventory.getCategory(folder_id);
|
|
validate_marketplacelistings(cat);
|
|
update_marketplace_category(folder_id);
|
|
gInventory.notifyObservers();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Private Modifiers
|
|
bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id, bool update)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it == mMarketplaceItems.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
(it->second).mListingId = listing_id;
|
|
|
|
if (update)
|
|
{
|
|
update_marketplace_category(folder_id, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::setCountOnHand(const LLUUID& folder_id, S32 count, bool update)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it == mMarketplaceItems.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
(it->second).mCountOnHand = count;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it == mMarketplaceItems.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LLUUID old_version_id = (it->second).mVersionFolderId;
|
|
if (old_version_id == version_id)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
(it->second).mVersionFolderId = version_id;
|
|
mVersionFolders.erase(old_version_id);
|
|
if (version_id.notNull())
|
|
{
|
|
mVersionFolders[version_id] = folder_id;
|
|
}
|
|
|
|
if (update)
|
|
{
|
|
update_marketplace_category(old_version_id, false);
|
|
update_marketplace_category(version_id, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::setActivationState(const LLUUID& folder_id, bool activate, bool update)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it == mMarketplaceItems.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
(it->second).mIsActive = activate;
|
|
|
|
if (update)
|
|
{
|
|
update_marketplace_category((it->second).mListingFolderId, false);
|
|
gInventory.notifyObservers();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMarketplaceData::setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update)
|
|
{
|
|
marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id);
|
|
if (it == mMarketplaceItems.end())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
(it->second).mEditURL = edit_url;
|
|
return true;
|
|
}
|
|
|