game: simple in-game IPFS image viewer

This commit is contained in:
Crypto City 2021-03-20 20:40:33 +00:00
parent 90efc53bf5
commit 64af90f54e
16 changed files with 460 additions and 31 deletions

View File

@ -0,0 +1,9 @@
WindowInfo
title External data
centered-relative-size: .9 .9
modal: 1
TBScrollContainer: id: "container", adapt-content: 1, scroll-mode: "auto", size: "available", gravity: "all"
TBLayout: id: "contents", axis: y, gravity: "all"
TBButton: id: "zoom-in", text: "+"
TBButton: id: "zoom-out", text: "-"

View File

@ -638,10 +638,15 @@ and a small one for hunting bears and moose otherwise.
company, the game cannot and will not enforce payment of these dividends.
</p>
<p>
Non Fungible Tokens may be created, and linked to arbitrary data stored on IPFS, though for
security reasons Townforge will not display that data in the game, and viewing it will require
using a web browser. The item creation form allows selecting a file to upload. You must be
running the IPFS daemon locally on default port 5001 for this to work.
Non Fungible Tokens may be created, and linked to arbitrary data stored on IPFS.
The item creation form allows selecting a file to upload. You must be running the IPFS daemon
locally on default port 5001 for this to work. Viewing does not require a local IPFS daemon,
only the item creator needs to be running one. Running your own will be faster though.
The game only supports displaying some types of image, including PNG and JPEG. Remember that
these images are user created, and their creator is potentially malicious.
<br>
For now, the item creator has to keep running the IPFS daemon or use an external pinning
service for the data to stay available.
</p>
<p>
A player may define items groups. Item groups are items themselves, but with no actual instances

2
external/tb vendored

@ -1 +1 @@
Subproject commit 46dfe0a7cc922ea47940759f7a7ac7c06f0fb3b7
Subproject commit 693801d05e5c927278cadaa6c0a156167cf38b84

View File

@ -7,6 +7,22 @@
#include "base58sn.h"
#include "ipfs.h"
#define TOWNFORGE_IPFS_IPV4_PEER "/ip4/78.47.87.82/tcp/4001/p2p/12D3KooWKcJQNKjFgA8ALhxn21wj8uF27oM3miprLydkUi9sDLJ1"
#define TOWNFORGE_IPFS_IPV6_PEER "/ip6/2a01:4f8:c2c:af4d::1/tcp/4001/p2p/12D3KooWKcJQNKjFgA8ALhxn21wj8uF27oM3miprLydkUi9sDLJ1"
static const struct
{
const char *address;
bool gateway;
bool ssl;
bool restricted;
}
ipfs_addresses[] = {
{ "127.0.0.1:5001", false, false, false },
{ "ipfs.townforge.net:443", true, true, true },
{ "gateway.ipfs.io:443", true, true, true },
};
std::string get_ipfs_hash(const crypto::hash &hash)
{
char data[34], b58[80];
@ -35,20 +51,35 @@ std::string ipfs_upload(const std::string &contents, const std::function<void(co
full_contents += "\r\n";
full_contents += "--" + boundary + "--\r\n";
const epee::net_utils::http::http_response_info* pri = NULL;
if (!http_client->set_server("127.0.0.1:5001", boost::none, epee::net_utils::ssl_support_t::e_ssl_support_disabled))
bool connected = false;
for (const auto &server: ipfs_addresses)
{
error("Failed to connect to local IPFS daemon, is it running on port 5001 ?");
if (server.gateway) // we don't support writing to gateways
continue;
if (server.restricted) // add doesn't work on those
continue;
if (!http_client->set_server(server.address, boost::none, server.ssl ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled))
continue;
if (!http_client->connect(std::chrono::seconds(5)))
continue;
if (!http_client->is_connected())
continue;
connected = true;
printf("Connected to %s\n", server.address);
break;
}
if (!connected)
{
error("Error connecting to IPFS");
return "";
}
if (!http_client->connect(std::chrono::seconds(5)))
if (!http_client->invoke("/api/v0/swarm/connect?arg=" TOWNFORGE_IPFS_IPV4_PEER, "POST", "", std::chrono::seconds(30), &pri))
{
error("Failed to connect to local IPFS daemon, is it running on port 5001 ?");
return "";
// ignore error, propagation will just be excruciatingly slow
}
if (!http_client->is_connected())
if (!http_client->invoke("/api/v0/swarm/connect?arg=" TOWNFORGE_IPFS_IPV6_PEER, "POST", "", std::chrono::seconds(30), &pri))
{
error("Failed to connect to local IPFS daemon, is it running on port 5001 ?");
return "";
// ignore error, propagation will just be excruciatingly slow
}
if (!http_client->invoke("/api/v0/add", "POST", full_contents, std::chrono::seconds(30), &pri, std::move(additional_params)))
{
@ -79,3 +110,60 @@ std::string ipfs_upload(const std::string &contents, const std::function<void(co
}
return hash;
}
bool ipfs_download(const std::string &hash, const std::function<void(const std::string&)> &error, std::string &data)
{
const std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client = net::http::client_factory().create();
epee::net_utils::http::fields_list additional_params;
const epee::net_utils::http::http_response_info* pri = NULL;
bool connected = false, gateway = false;
for (const auto &server: ipfs_addresses)
{
if (!http_client->set_server(server.address, boost::none, server.ssl ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled))
continue;
if (!http_client->connect(std::chrono::seconds(5)))
continue;
if (!http_client->is_connected())
continue;
connected = true;
gateway = server.gateway;
printf("Connected to %s\n", server.address);
break;
}
if (!connected)
{
error("Error connecting to IPFS");
return false;
}
if (gateway)
{
const std::string url = "/ipfs/" + hash;
if (!http_client->invoke(url, "GET", "", std::chrono::seconds(30), &pri, std::move(additional_params)))
{
error("Error downloading file: HTTP request failed");
return false;
}
}
else
{
const std::string url = "/api/v0/cat?arg=/ipfs/" + hash;
if (!http_client->invoke(url, "POST", "", std::chrono::seconds(30), &pri, std::move(additional_params)))
{
error("Error downloading file: API failed");
return false;
}
}
if (!pri)
{
error("Error downloading file: internal error");
return false;
}
if (pri->m_response_code != 200)
{
error("Error downloading file: API returned error " + std::to_string(pri->m_response_code));
return false;
}
data = pri->m_body;
return true;
}

View File

@ -6,3 +6,4 @@
std::string get_ipfs_hash(const crypto::hash &hash);
std::string ipfs_upload(const std::string &contents, const std::function<void(const std::string&)> &error);
bool ipfs_download(const std::string &hash, const std::function<void(const std::string&)> &error, std::string &data);

View File

@ -86,6 +86,7 @@ set(game_sources
ui-configure-script.cc
ui-console.cc
ui-credits.cc
ui-data-viewer.cc
ui-define-attribute.cc
ui-dice-roll.cc
ui-dividend.cc
@ -170,6 +171,7 @@ set(game_headers
ui-configure-script.h
ui-console.h
ui-credits.h
ui-data-viewer.h
ui-define-attribute.h
ui-dice-roll.h
ui-dividend.h

View File

@ -276,11 +276,12 @@ void process_overrides(const GameState *game, std::string &s, const std::vector<
CATCH_ENTRY_L0("ProcessOverrides", void());
}
std::string get_item_info(const GameState *game, const std::shared_ptr<GameWallet> &w, uint32_t id)
std::pair<std::string, crypto::hash> get_item_info(const GameState *game, const std::shared_ptr<GameWallet> &w, uint32_t id)
{
std::stringstream ss;
bool fixed_supply = false;
bool is_collectible_coin = false, is_mortgage = false;
crypto::hash ipfs_hash = crypto::null_hash;
ss << "ID: " << id << "\n";
@ -300,9 +301,8 @@ std::string get_item_info(const GameState *game, const std::shared_ptr<GameWalle
std::string name, primary_description, secondary_description, mime_type;
bool ignore, is_group, is_public;
std::vector<uint64_t> user_data;
crypto::hash hash;
if (w->get_cc_custom_item_data(id, creation_height, creator, amount, name, is_group, is_public, group, primary_description, secondary_description, ignore, user_data, hash, mime_type))
if (w->get_cc_custom_item_data(id, creation_height, creator, amount, name, is_group, is_public, group, primary_description, secondary_description, ignore, user_data, ipfs_hash, mime_type))
{
if (id == COINS_ITEM_GROUP)
{
@ -391,11 +391,11 @@ std::string get_item_info(const GameState *game, const std::shared_ptr<GameWalle
else
ss << "Failed to get item data\n";
if (hash != crypto::null_hash)
if (ipfs_hash != crypto::null_hash)
{
const std::string ipfs_hash = get_ipfs_hash(hash);
ss << "IPFS multihash: ipfs://ipfs/" << ipfs_hash << "\n";
ss << "URL: https://ipfs.io/ipfs/" << ipfs_hash << "\n";
const std::string ipfs_hash_str = get_ipfs_hash(ipfs_hash);
ss << "IPFS multihash: ipfs://ipfs/" << ipfs_hash_str << "\n";
ss << "URL: https://ipfs.io/ipfs/" << ipfs_hash_str << "\n";
if (!mime_type.empty())
ss << "MIME type: " << mime_type << "\n";
}
@ -415,7 +415,7 @@ std::string get_item_info(const GameState *game, const std::shared_ptr<GameWalle
if (points > 0)
ss << "Heating points: " << points << "\n";
return ss.str();
return std::make_pair(ss.str(), ipfs_hash);
}
}

View File

@ -18,6 +18,6 @@ std::string get_building_name(const GameState *game, uint32_t flag_id);
std::string get_command_string(const GameState *game, const cryptonote::cc_command_t &cmd);
int32_t get_crop_yield(const GameState *game, const std::shared_ptr<GameWallet> &w, uint8_t crop, uint64_t sow_height, uint8_t geothermal_heating, bool frost_resistant_vegetables, bool fast_frost_damage_recovery);
void process_overrides(const GameState *game, std::string &s, const std::vector<std::tuple<uint8_t, uint32_t, std::string>> &overrides, bool in_script);
std::string get_item_info(const GameState *Game, const std::shared_ptr<GameWallet> &w, uint32_t item);
std::pair<std::string, crypto::hash> get_item_info(const GameState *Game, const std::shared_ptr<GameWallet> &w, uint32_t item);
}

203
src/game/ui-data-viewer.cc Normal file
View File

@ -0,0 +1,203 @@
#include <limits.h>
#include <math.h>
#include <limits>
#include <memory>
#include "Urho3D/Core/Context.h"
#include "Urho3D/Resource/ResourceCache.h"
#include "Urho3D/IO/Deserializer.h"
#include "Urho3D/UI/UI.h"
#include "Urho3D/UI/UIEvents.h"
#include <Urho3D/Graphics/Graphics.h>
#include <Urho3D/Graphics/Texture2D.h>
#include <tb/tb_widgets_common.h>
#include <tb/tb_widgets_reader.h>
#include <tb/tb_scroll_container.h>
#include <tb/image/tb_image_widget.h>
#include "UTBRendererBatcher.h"
#include "ui-tb-message-box.h"
#include "ui-tb-animated-image-widget.h"
#include "ui-data-viewer.h"
using namespace Urho3D;
using namespace tb;
template<typename T> static T zoom_value(T value, int zoom)
{
return (T)(0.5 + round(value * pow(M_SQRT2, (double)zoom)));
}
UIDataViewerDialog::UIDataViewerDialog(Context *ctx, const std::string &data):
UITBWindow(ctx, "cc/data-viewer.tb.txt"),
dataWidget(NULL),
zoom(0),
dataWidth(0),
dataHeight(0)
{
contents = GetWidgetByIDAndType<TBLayout>(TBIDC("contents"));
container = GetWidgetByIDAndType<TBScrollContainer>(TBIDC("container"));
zoomIn = GetWidgetByIDAndType<TBButton>(TBIDC("zoom-in"));
zoomOut = GetWidgetByIDAndType<TBButton>(TBIDC("zoom-out"));
Load(data);
Layout();
SubscribeToEvent(this, E_TB_WIDGET_EVENT, URHO3D_HANDLER(UIDataViewerDialog, HandleTBMessage));
SubscribeToEvent(this, E_TB_WINDOW_CLOSED, URHO3D_HANDLER(UIDataViewerDialog, HandleClose));
}
UIDataViewerDialog::~UIDataViewerDialog()
{
}
void UIDataViewerDialog::RegisterObject(Context* context)
{
context->RegisterFactory<UIDataViewerDialog>();
}
bool UIDataViewerDialog::Load(const std::string &data)
{
TBImageWidget *w = new TBImageWidget();
contents->GetContentRoot()->AddChild(w);
w->SetImage(data.data(), data.size());
if (w->GetImage().Width() > 0 && w->GetImage().Height() > 0)
{
dataWidget = w;
dataWidth = w->GetImage().Width();
dataHeight = w->GetImage().Height();
return true;
}
w->RemoveFromParent();
delete w;
class MemoryLoader: public Deserializer {
public:
MemoryLoader(const std::string &data): Deserializer(data.size()), data_(data) {}
virtual unsigned Read(void* dest, unsigned size) { unsigned bytes = std::min(size, size_ - position_); memcpy(dest, data_.data() + position_, bytes); return bytes; }
virtual unsigned Seek(unsigned position) { position_ = position; return position_; }
private:
const std::string data_;
} loader(data);
Image image(context_);
if (image.Load(loader))
{
const unsigned char *image_data = image.GetData();
SharedPtr<Texture2D> texture(new Texture2D(context_));
texture->SetFilterMode(Urho3D::TextureFilterMode::FILTER_DEFAULT);
texture->SetNumLevels(1);
const unsigned image_width = image.GetWidth();
const unsigned image_height = image.GetHeight();
texture->SetSize(image_width, image_height, Graphics::GetRGBAFormat());
unsigned idx = 0;
uint8_t *rgb = new uint8_t[image_width * image_height * 4];
memset(rgb, 0, image_width * image_height * 4);
for (unsigned int y = 0; y < image_height; ++y)
{
for (unsigned int x = 0; x < image_width; ++x)
{
rgb[idx*4+0] = image_data[idx*3+0];
rgb[idx*4+1] = image_data[idx*3+1];
rgb[idx*4+2] = image_data[idx*3+2];
rgb[idx*4+3] = 0xff;
++idx;
}
}
std::shared_ptr<tb::TBBitmap> bitmap(UTBRendererBatcher::Singleton().CreateBitmap(image_width, image_height, (uint32_t*)rgb));
std::vector<std::pair<std::shared_ptr<tb::TBBitmap>, unsigned int>> textures;
textures.push_back(std::make_pair(std::move(bitmap), 1));
delete[] rgb;
UITBAnimatedImageWidget *aw = new UITBAnimatedImageWidget();
contents->GetContentRoot()->AddChild(aw);
aw->SetAnimation(textures);
dataWidget = aw;
dataWidth = image_width;
dataHeight = image_height;
return true;
}
new MessageBox(context_, "Failed to load contents from downloaded data: unknown format");
Close();
dataWidget = NULL;
return false;
}
void UIDataViewerDialog::Layout()
{
const auto base = contents->GetContentRoot()->GetRect();
zoomIn->SetPosition({base.x + 32, base.y + 64});
const auto button_height = zoomIn->GetRect().h;
zoomOut->SetPosition({base.x + 32, base.y + 64 + button_height * 3 / 2});
const TBRect rect = zoomIn->GetRect();
zoomOut->SetSize(rect.w, rect.h);
}
TBPoint UIDataViewerDialog::GetMidPoint() const
{
const TBRect rect = container->GetRect();
const TBWidget::ScrollInfo scroll_info = container->GetScrollInfo();
return TBPoint{
zoom_value(rect.w / 2 - scroll_info.x, zoom),
zoom_value(rect.h / 2 - scroll_info.y, zoom)
};
}
void UIDataViewerDialog::HandleZoomIn(StringHash eventType, VariantMap& eventData)
{
++zoom;
TBRect rect = dataWidget->GetRect();
rect.w = zoom_value(dataWidth, zoom);
rect.h = zoom_value(dataHeight, zoom);
dataWidget->SetRect(rect);
}
void UIDataViewerDialog::HandleZoomOut(StringHash eventType, VariantMap& eventData)
{
--zoom;
TBRect rect = dataWidget->GetRect();
rect.w = zoom_value(dataWidth, zoom);
rect.h = zoom_value(dataHeight, zoom);
dataWidget->SetRect(rect);
}
void UIDataViewerDialog::HandleOK(StringHash eventType, VariantMap& eventData)
{
Close();
}
void UIDataViewerDialog::HandleTBMessage(StringHash eventType, VariantMap& eventData)
{
#define CONNECT(name, function) do { if (ev->target->GetID() == TBIDC(name)) function(eventType, eventData); } while(0)
using namespace TBWidgetEventNamespace;
TBWidgetEvent *ev = (TBWidgetEvent*)eventData[P_WIDGET_EVENT].GetVoidPtr();
if (ev->type == EVENT_TYPE_CLICK)
{
CONNECT("ok", HandleOK);
CONNECT("cancel", HandleCancel);
CONNECT("zoom-in", HandleZoomIn);
CONNECT("zoom-out", HandleZoomOut);
}
else if (ev->type == EVENT_TYPE_CHANGED)
{
}
#undef CONNECT
}
void UIDataViewerDialog::HandleClose(StringHash eventType, VariantMap& eventData)
{
VariantMap& newEventData = GetEventDataMap();
SendEvent(E_DATA_VIEWER_CLOSED, newEventData);
// Self destruct
Close();
}
void UIDataViewerDialog::HandleCancel(StringHash eventType, VariantMap& eventData)
{
VariantMap& newEventData = GetEventDataMap();
SendEvent(E_DATA_VIEWER_CLOSED, newEventData);
// Self destruct
Close();
}

57
src/game/ui-data-viewer.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef UI_DATA_VIEWER_H
#define UI_DATA_VIEWER_H
#include <vector>
#include <Urho3D/Core/Object.h>
#include <Urho3D/Container/Str.h>
#include <Urho3D/Container/Ptr.h>
#include <Urho3D/Math/StringHash.h>
#include <tb/tb_select_item.h>
#include "ui-tb-window.h"
namespace Urho3D
{
class Context;
}
namespace tb
{
class TBLayout;
class TBScrollContainer;
class TBButton;
class TBPoint;
}
URHO3D_EVENT(E_DATA_VIEWER_CLOSED, DataViewerClosed) {}
class UIDataViewerDialog: public UITBWindow
{
public:
UIDataViewerDialog(Urho3D::Context *ctx, const std::string &data = "");
virtual ~UIDataViewerDialog() override;
static void RegisterObject(Urho3D::Context* context);
private:
void HandleZoomIn(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleZoomOut(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleOK(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleCancel(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleTBMessage(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleClose(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
bool Load(const std::string &data);
void Layout();
tb::TBPoint GetMidPoint() const;
private:
tb::TBScrollContainer *container;
tb::TBLayout *contents;
tb::TBButton *zoomIn;
tb::TBButton *zoomOut;
tb::TBWidget *dataWidget;
int zoom;
unsigned int dataWidth, dataHeight;
};
#endif

View File

@ -10,6 +10,7 @@
#include <tb/tb_tab_container.h>
#include <tb/image/tb_image_widget.h>
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "common/ipfs.h"
#include "cc/cc_config.h"
#include "cc/cc.h"
#include "cc/cc_influence.h"
@ -32,6 +33,7 @@
#include "ui-ignore.h"
#include "ui-fight-fire.h"
#include "ui-trade.h"
#include "ui-data-viewer.h"
#include "ui-tb-message-box.h"
#include "caching-source-builder.h"
#include "ui-player-info.h"
@ -1862,18 +1864,62 @@ void UIPlayerInfoDialog::UpdateItemInfo(const std::shared_ptr<GameWallet> &w)
itemInfoWidget->GetContentRoot()->DeleteAllChildren();
if (id == 0)
return;
if (game->ignore_item(id))
return;
const std::string info = game_util::get_item_info(game, w, id);
const std::pair<std::string, crypto::hash> info = game_util::get_item_info(game, w, id);
TBLayout *layout = new TBLayout();
layout->SetAxis(AXIS_Y);
itemInfoWidget->GetContentRoot()->AddChild(layout);
TBEditField *details = new TBEditField();
itemInfoWidget->GetContentRoot()->AddChild(details);
layout->AddChild(details);
details->SetReadOnly(true);
details->SetMultiline(true);
details->SetAdaptToContentSize(true);
details->SetText(info.c_str());
details->SetText(info.first.c_str());
details->SetTextAlign(TB_TEXT_ALIGN_LEFT);
details->SetSkinBg(TBIDC("0"));
details->InvalidateLayout(INVALIDATE_LAYOUT_RECURSIVE);
if (info.second != crypto::null_hash)
{
TBButton *view = new TBButton();
layout->AddChild(view);
view->SetText("View");
view->SetID("view-ipfs");
view->data.SetString(get_ipfs_hash(info.second).c_str());
TBEditField *warning = new TBEditField();
layout->AddChild(warning);
warning->SetStyling(true);
warning->SetText("<color #d48309>This button will load and attempt to display potentially malicious external player created data</color>");
warning->SetReadOnly(true);
warning->SetMultiline(true);
warning->SetAdaptToContentSize(true);
warning->SetTextAlign(TB_TEXT_ALIGN_CENTER);
warning->SetSkinBg(TBIDC("0"));
}
layout->InvalidateLayout(INVALIDATE_LAYOUT_RECURSIVE);
}
void UIPlayerInfoDialog::HandleViewIPFS(StringHash eventType, VariantMap& eventData)
{
TBWidgetEvent *ev = (TBWidgetEvent*)eventData[TBWidgetEventNamespace::P_WIDGET_EVENT].GetVoidPtr();
const TBStr hash = ev->target->data.GetString();
std::string data;
if (!ipfs_download(hash.CStr(), [this](const std::string &msg) {
new MessageBox(context_, "Error downloading item data: " + msg);
}, data))
return;
ShowExternal(data);
}
void UIPlayerInfoDialog::ShowExternal(const std::string &data)
{
new UIDataViewerDialog(context_, data);
}
void UIPlayerInfoDialog::HandleTBMessage(StringHash eventType, VariantMap& eventData)
@ -1899,6 +1945,7 @@ void UIPlayerInfoDialog::HandleTBMessage(StringHash eventType, VariantMap& event
CONNECT("fight-fire", HandleFightFire);
CONNECT("invitation-generate", HandleGenerateInvitation);
CONNECT("invitation-copy", HandleCopyInvitation);
CONNECT("view-ipfs", HandleViewIPFS);
HeaderButton *hb = target.Get() ? TBSafeCast<HeaderButton>(target.Get()) : NULL;
if (hb)

View File

@ -84,6 +84,7 @@ private:
void HandleGenerateInvitation(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleCopyInvitation(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleShowItemInfo(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void HandleViewIPFS(Urho3D::StringHash eventType, Urho3D::VariantMap& eventData);
void InitColumnsList();
void FillData(const std::shared_ptr<GameWallet> &w, uint32_t player_id);
@ -93,6 +94,7 @@ private:
void CreateHistoryNavigationWidgets();
void UpdateRunningOut();
void UpdateItemInfo(const std::shared_ptr<GameWallet> &w);
void ShowExternal(const std::string &data);
private:
class FlagItem: public tb::TBGenericStringItem

View File

@ -532,7 +532,7 @@ std::string UITradeDialog::GetMarketDetails(const std::shared_ptr<GameWallet> &w
return ss.str();
}
return game_util::get_item_info(game, w, id);
return game_util::get_item_info(game, w, id).first;
}
void UITradeDialog::UpdateDescriptions(const std::shared_ptr<GameWallet> &w)

View File

@ -4635,10 +4635,14 @@ namespace cryptonote
BlockchainDB &db = m_core.get_blockchain_storage().get_db();
if (req.ids.empty())
{
db.for_all_cc_custom_items([&res](const cc::cc_custom_item_t &cid) {
db.for_all_cc_custom_items([&req, &res](const cc::cc_custom_item_t &cid) {
std::string cid_hash = cid.hash == crypto::null_hash ? "" : epee::string_tools::pod_to_hex(cid.hash);
std::string multihash = cid.hash == crypto::null_hash ? "" : get_ipfs_hash(cid.hash);
res.items.push_back({cid.id, cid.creator, cid.creation_height, 0, cid.is_group, cid.is_public, cid.group, cid.ignore, cid.name, cid.primary_description, cid.secondary_description, cid.user_data, std::move(cid_hash), std::move(multihash), cid.mime_type});
std::string ipfs_data, ipfs_error;
if (req.include_ipfs_data && cid.hash != crypto::null_hash)
if (!ipfs_download(multihash, [&ipfs_error](const std::string &msg) { ipfs_error = msg; }, ipfs_data))
ipfs_data.clear();
res.items.push_back({cid.id, cid.creator, cid.creation_height, 0, cid.is_group, cid.is_public, cid.group, cid.ignore, cid.name, cid.primary_description, cid.secondary_description, cid.user_data, std::move(cid_hash), std::move(multihash), cid.mime_type, std::move(ipfs_data), std::move(ipfs_error)});
return true;
});
for (auto &e: res.items)
@ -4658,7 +4662,11 @@ namespace cryptonote
const uint64_t amount = db.get_cc_item_count(id);
std::string cid_hash = cid.hash == crypto::null_hash ? "" : epee::string_tools::pod_to_hex(cid.hash);
std::string multihash = cid.hash == crypto::null_hash ? "" : get_ipfs_hash(cid.hash);
res.items.push_back({cid.id, cid.creator, cid.creation_height, amount, cid.is_group, cid.is_public, cid.group, cid.ignore, cid.name, cid.primary_description, cid.secondary_description, std::move(cid.user_data), std::move(cid_hash), std::move(multihash), std::move(cid.mime_type)});
std::string ipfs_data, ipfs_error;
if (req.include_ipfs_data && cid.hash != crypto::null_hash)
if (!ipfs_download(multihash, [&ipfs_error](const std::string &msg) { ipfs_error = msg; }, ipfs_data))
ipfs_data.clear();
res.items.push_back({cid.id, cid.creator, cid.creation_height, amount, cid.is_group, cid.is_public, cid.group, cid.ignore, cid.name, cid.primary_description, cid.secondary_description, std::move(cid.user_data), std::move(cid_hash), std::move(multihash), std::move(cid.mime_type), std::move(ipfs_data), std::move(ipfs_error)});
}
}
}

View File

@ -3709,9 +3709,11 @@ namespace cryptonote
struct request_t
{
std::vector<uint32_t> ids;
bool include_ipfs_data;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(ids)
KV_SERIALIZE_OPT(include_ipfs_data, false)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@ -3733,6 +3735,8 @@ namespace cryptonote
std::string hash;
std::string ipfs_multihash;
std::string mime_type;
std::string ipfs_data;
std::string ipfs_error;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(id)
@ -3750,6 +3754,8 @@ namespace cryptonote
KV_SERIALIZE(hash)
KV_SERIALIZE(ipfs_multihash)
KV_SERIALIZE(mime_type)
KV_SERIALIZE(ipfs_data)
KV_SERIALIZE(ipfs_error)
END_KV_SERIALIZE_MAP()
};

View File

@ -825,11 +825,12 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(cc_get_special_events)
def cc_get_custom_items(self, ids = []):
def cc_get_custom_items(self, ids = [], include_ipfs_data = False):
cc_get_custom_items = {
'method': 'cc_get_custom_items',
'params': {
'ids': ids,
'include_ipfs_data': include_ipfs_data,
},
'jsonrpc': '2.0',
'id': '0'