diff --git a/Makefile b/Makefile
index 0f9ebfe..1c3a488 100644
--- a/Makefile
+++ b/Makefile
@@ -15,9 +15,9 @@
# `git clone --recursive git.nugget.fun/nugget/gunkiss`. The paths below are the default for the repository, but
# they can be edited as necessary.
-#######################
-# Location parameters #
-#######################
+#########
+# Paths #
+#########
# Location of project specific source files
SRC_DIR := src/
@@ -38,7 +38,7 @@ CXX := clang++
# Location of SDL config program
SDLCONFIG := $(HOME)/local/sdl/bin/sdl2-config
-# Edit to point to the location of BPmono.ttf
+# Include BPmono.ttf in the project
CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" .
#############################
@@ -89,7 +89,7 @@ $(SRC_DIR)Pudding.o : $(SRC_H_FILES) $(SB_H_FILES)
# Linux build #
###############
-linux : CFLAGS = -g -Wall -Wextra -O0 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) -I$(HOME)/local/zbar/include \
+linux : CFLAGS = -g -Wall -Wextra -O1 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) -I$(HOME)/local/zbar/include \
-I $(HOME)/local/opencv/include/opencv4 -I $(HOME)/ext/software/emsdk/upstream/emscripten/system/include
linux : CXXFLAGS = $(CFLAGS) --std=c++17
linux : LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -lcurl \
@@ -108,14 +108,8 @@ linux : $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPri
EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
EMSCRIPTEN_CFLAGS = -O1 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \
- --no-heap-copy -I $(SB_LIB_DIR) -I $(SB_SRC_DIR) -I $(HOME)/local/zbar/include \
- -I $(HOME)/ext/software/opencv-4.6.0/modules/videoio/include/ \
- -I $(HOME)/ext/software/opencv-4.6.0/modules/core/include/ \
- -I $(HOME)/ext/software/opencv-4.6.0/modules/highgui/include/ \
- -I $(HOME)/ext/software/opencv-4.6.0/modules/imgproc/include/ \
- -I $(HOME)/ext/software/opencv-4.6.0/modules/imgcodecs/include/ \
- -I $(HOME)/ext/software/opencv-4.6.0/build_wasm/
-EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
+ --no-heap-copy -I $(SB_LIB_DIR) -I $(SB_SRC_DIR) -I $(HOME)/local/zbar/include -I $(HOME)/local/opencv/include/opencv4
+EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main', '_malloc']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
-sLLD_REPORT_UNDEFINED -s FETCH --bind $(wildcard $(addprefix $(HOME)/ext/software/opencv-4.6.0/build_wasm/lib/,*.a)) \
$(HOME)/ext/software/ZBar/zbar/.libs/libzbar.a
EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "config.json"@/ --preload-file "resource/"@/"resource/" \
diff --git a/config.json b/config.json
index 9708afe..accda9d 100644
--- a/config.json
+++ b/config.json
@@ -3,11 +3,11 @@
{
"dimensions": [460, 768],
"framerate": 60,
- "title": "Gunkiss",
+ "title": "Pudding",
"debug": false,
- "render driver": "opengles2",
+ "render driver": "opengl",
"show-cursor": true,
- "camera-resolution": [1280, 720]
+ "camera-resolution": [320, 240]
},
"configuration":
@@ -21,8 +21,8 @@
"print-frame-length-history": ["CTRL", "SHIFT", "h"],
"toggle-camera": ["CTRL", "c"],
"toggle-item": ["CTRL", "i"],
- "effect": ["CTRL", "e"],
- "tile": ["CTRL", "t"]
+ "effect": ["e"],
+ "tile": ["t"]
},
"recording":
@@ -55,27 +55,31 @@
"scan":
{
"enabled": true,
- "json-save": true,
+ "json-save": false,
"json-save-directory": "local/scans",
"barcode": "",
- "capture-device": "/dev/video0"
+ "capture-device": "/dev/video0",
+ "brightness-addition": 10,
+ "contrast-multiplication": 1.3,
+ "camera-device-id": 0
},
"api":
{
- "user-agent": "Custom pudding creation game under development for https://shampoo.ooo",
- "nutronix-app-id": "ea0f2e7e",
- "nutronix-app-key": "39218dde526dd3349daa028deda518ae",
+ "user-agent": "Custom pudding creation game under development at https://mario.shampoo.ooo",
+ "nutritionix-app-id": "ea0f2e7e",
+ "nutritionix-app-key": "39218dde526dd3349daa028deda518ae",
"edamam-app-id": "c23b139f",
"edamam-app-key": "c54cf8c997534caf7ee92b1ccc7d95a3",
"best-buy-api-key": "vAC23XA5YWBzaYiGtOkoNlXZ",
"giantbomb-api-key": "91a395231f4e1fd9f9ba8840c52a61cda343cd70",
- "nutronix-enabled": false,
- "edamam-enabled": false,
+ "google-books-api-key": "AIzaSyBD9wXIlBJ6UrXXDIY03k6s0oR1q6ByETQ",
+ "nutritionix-enabled": true,
+ "edamam-enabled": true,
"open-food-enabled": true,
"open-products-enabled": true,
"best-buy-enabled": true,
- "google-books-enabled": true
+ "google-books-enabled": false
},
"pudding":
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..99642b2
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.html b/index.html
index a996fc5..a1333ed 100644
--- a/index.html
+++ b/index.html
@@ -1,104 +1,128 @@
+
+
+
-
+
-
-
-
diff --git a/lib/sb b/lib/sb
index b1fb77b..24f6d3e 160000
--- a/lib/sb
+++ b/lib/sb
@@ -1 +1 @@
-Subproject commit b1fb77b1c8a2902fde711ede1a45b459013dc876
+Subproject commit 24f6d3ed3d4962a88078c5024473834812968d1a
diff --git a/src/Item.cpp b/src/Item.cpp
index a9c2324..27a73e7 100644
--- a/src/Item.cpp
+++ b/src/Item.cpp
@@ -1,10 +1,10 @@
/* _______________ ,-------------------------------------------------.
- //`````````````\\ \ \
- //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
- //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
- // \\ \ \
- // \\ \ zlib licensed code at [git.nugget.fun/pudding] \
- // ☆ GUNKISS ☆ \\ \ \
+//`````````````\\ \ \
+//~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+//=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+// \\ \ \
+// \\ \ zlib licensed code at [git.nugget.fun/pudding] \
+// ☆ GUNKISS ☆ \\ \ \
//_________________________\\ `-------------------------------------------------*/
#include "Item.hpp"
@@ -69,6 +69,13 @@ std::string Item::full_name() const
name += " ";
}
name += product_name();
+
+ /* If no names have been set yet, try the UPC as a name */
+ if (name == "")
+ {
+ name = upc();
+ }
+
return name;
}
@@ -125,3 +132,9 @@ void Item::to_last()
{
carousel.end(item_view.textures());
}
+
+std::ostream& std::operator<<(std::ostream& out, const Item& item)
+{
+ out << item.full_name();
+ return out;
+}
diff --git a/src/Item.hpp b/src/Item.hpp
index 89f68dc..4738658 100644
--- a/src/Item.hpp
+++ b/src/Item.hpp
@@ -68,4 +68,16 @@ public:
};
+namespace std
+{
+ /*!
+ * Support passing item objects to the global stream operator.
+ *
+ * @param out The output stream
+ * @param item The item to be printed
+ * @return The submitted output stream with the text representation of item added
+ */
+ std::ostream& operator<<(std::ostream& out, const Item& item);
+}
+
#endif
diff --git a/src/Pudding.cpp b/src/Pudding.cpp
index 0f4cd3a..c5e67f7 100644
--- a/src/Pudding.cpp
+++ b/src/Pudding.cpp
@@ -75,7 +75,7 @@ Pudding::Pudding()
void Pudding::load_pudding_model(float top_radius, float base_radius, int ring_vertex_count, int layer_count, float min_y,
float max_y, float gradient_position)
{
- size_t ii;
+ std::size_t ii;
const glm::vec3 *layer_top_color, *layer_bottom_color;
const glm::vec2 *start_vertex, *end_vertex;
float layer_top_y, layer_top_percent, layer_base_y, layer_base_percent, u_step = 1.0f / ring_vertex_count, ring_start_vertex_u;
@@ -284,16 +284,11 @@ void Pudding::load_pads()
next_button.rotation(glm::radians(180.0f));
}
-/*!
- * Try to create cv::VideoCapture object using device ID #0. If successful, this will also create a GL texture ID and
- * storage for the camera frame on the GPU, so it must be called after GL context has been created. Create and detach
- * a thread which will continuously read frame data.
- */
void Pudding::open_camera()
{
#ifndef __EMSCRIPTEN__
/* Open the OpenCV capture, using device ID #0 to get the default attached camera. */
- int device_id = 0;
+ int device_id = configuration()["scan"]["camera-device-id"];
capture.open(device_id);
std::ostringstream message;
if (capture.isOpened())
@@ -432,6 +427,15 @@ void Pudding::respond(SDL_Event& event)
if (over_camera_button)
{
camera_switch.connect();
+
+#ifndef __EMSCRIPTEN__
+ /* If the camera did not open, this failed, so unflip the switch */
+ if (!capture.isOpened())
+ {
+ camera_switch.disconnect();
+ }
+#endif
+
}
else if (over_inventory_button)
{
@@ -489,151 +493,156 @@ void Pudding::respond(SDL_Event& event)
*/
void Pudding::add_item(const std::string& upc)
{
- Item item;
- item.upc(upc);
+ /* Store the UPC code in the incoming item object */
+ incoming_item.upc(upc);
+
if (configuration()["api"]["open-food-enabled"])
{
- incorporate_open_api(item, OPEN_FOOD_API_URL);
+ web_get_bytes(OPEN_FOOD_API_URL + upc, std::bind(&Pudding::incorporate_open_api, this, std::placeholders::_1, std::placeholders::_2));
}
if (configuration()["api"]["open-products-enabled"])
{
- incorporate_open_api(item, OPEN_PRODUCTS_API_URL);
+ web_get_bytes(OPEN_PRODUCTS_API_URL + upc, std::bind(&Pudding::incorporate_open_api, this, std::placeholders::_1, std::placeholders::_2));
}
- if (configuration()["api"]["nutronix-enabled"])
+ if (configuration()["api"]["nutritionix-enabled"])
{
- incorporate_nutronix_api(item);
+ /* Nutritionix requires API keys in headers for validation */
+ web_get_bytes(NUTRITIONIX_API_URL + upc, std::bind(&Pudding::incorporate_nutritionix_api, this, std::placeholders::_1, std::placeholders::_2), {
+ "x-app-id", configuration()["api"]["nutritionix-app-id"].get(),
+ "x-app-key", configuration()["api"]["nutritionix-app-key"].get()
+ });
}
if (configuration()["api"]["edamam-enabled"])
{
- incorporate_edamam_api(item);
+ /* Build API request by concatenating URL and query string */
+ std::stringstream url;
+ url << "https://api.edamam.com/api/food-database/v2/parser?upc=" << upc << "&app_id=" <<
+ configuration()["api"]["edamam-app-id"].get() << "&app_key=" <<
+ configuration()["api"]["edamam-app-key"].get();
+ web_get_bytes(url.str(), std::bind(&Pudding::incorporate_edamam_api, this, std::placeholders::_1, std::placeholders::_2));
}
if (configuration()["api"]["best-buy-enabled"])
{
- incorporate_best_buy_api(item);
+ /* Build API request by concatenating URL and query string */
+ std::stringstream url;
+ url << BEST_BUY_API_URL_1 << upc << BEST_BUY_API_URL_2 << configuration()["api"]["best-buy-api-key"].get();
+ web_get_bytes(url.str(), std::bind(&Pudding::incorporate_best_buy_api, this, std::placeholders::_1, std::placeholders::_2));
}
if (configuration()["api"]["google-books-enabled"])
{
- incorporate_google_books_api(item);
- }
- if (item.texture_count() > 0)
- {
- items.push_back(item);
- /* Set item index to end so newest item will display. */
- item_carousel.end(items);
- /* Move the camera button away from center to make room for inventory button if this is the first item added. */
- if (items.size() == 1)
- {
- const nlohmann::json& interface = configuration()["interface"];
- camera_button.translation({-1.0f * interface["main-button-double-x"].get(), interface["main-button-y"]});
- }
- }
- else
- {
- std::ostringstream message;
- message << "discarding item, no images found for " << upc;
- sb::Log::log(message);
+ std::stringstream url;
+ url << GOOGLE_BOOKS_API_URL << upc << "&key=" << configuration()["api"]["google-books-api-key"].get();
+ web_get_bytes(url.str(), std::bind(&Pudding::incorporate_google_books_api, this, std::placeholders::_1, std::placeholders::_2));
}
}
-/* Look for item upc in the Open Food/Products API and use the result to fill out item properties if found. */
-void Pudding::incorporate_open_api(Item& item, const std::string& api_url)
+void Pudding::incorporate_open_api(const std::vector& json_bytes, const std::string& url)
{
- std::ostringstream checking_message;
- checking_message << "checking " << api_url;
- sb::Log::log(checking_message);
- nlohmann::json json = json_from_url(api_url + item.upc());
- /* test that should determine if an Open Food API response is not empty */
+ std::ostringstream message;
+ message << "Processing " << (json_bytes.size() / 100.0) << "KB from the Open Food/Products API";
+ sb::Log::log(message);
+
+ /* Use the nlohmann library to parse the raw JSON byte data retrieved from a web request. */
+ nlohmann::json json = nlohmann::json::parse(json_bytes);
+ std::stringstream json_formatted;
+ json_formatted << std::setw(4) << json << std::endl;
+ sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
+
+ /* Test that should determine if an Open Food API response is not empty */
if (json.value("status", 0) && json.contains("product"))
{
+ std::ostringstream message;
if (json["product"].value("image_url", "") != "")
{
std::string image_url = json["product"]["image_url"];
- sb::Texture texture = texture_from_image_url(image_url);
- if (texture.generated())
- {
- item.texture(texture, image_url);
- }
+ std::ostringstream message;
+ message << "Found image URL for item " << incoming_item << " from Open API at " << image_url;
+ sb::Log::log(message);
+ web_get_bytes(image_url, std::bind(&Pudding::store_web_image, this, std::placeholders::_1, std::placeholders::_2));
}
- item.brand_name(json["product"].value("brands", ""));
- item.product_name(json["product"].value("product_name", ""));
- if (api_url == OPEN_FOOD_API_URL)
+ else
{
- save_item_json(json, item, "Open_Food_API");
- }
- else if (api_url == OPEN_PRODUCTS_API_URL)
- {
- save_item_json(json, item, "Open_Products_API");
+ message << "No images found at Open API for " << incoming_item;
}
+ sb::Log::log(message);
+ incoming_item.brand_name(json["product"].value("brands", ""));
+ incoming_item.product_name(json["product"].value("product_name", ""));
+ save_item_json(json, incoming_item, "Open_Food_and_Products_API");
}
else
{
- std::ostringstream results_message;
- results_message << "no results from " << api_url;
- sb::Log::log(results_message);
+ sb::Log::log("No item found in JSON from Open API");
}
}
-/* Look for item upc in the Nutronix API, and use the result to fill out item properties if found
- */
-void Pudding::incorporate_nutronix_api(Item& item)
+void Pudding::incorporate_nutritionix_api(const std::vector& json_bytes, const std::string& url)
{
- sb::Log::log("checking Nutronix API");
- /* Nutronix requires API keys in headers for validation */
- nlohmann::json json = json_from_url(
- NUTRONIX_API_URL + item.upc(), {
- "x-app-id: " + configuration()["api"]["nutronix-app-id"].get(),
- "x-app-key: " + configuration()["api"]["nutronix-app-key"].get()
- });
- /* test that should determine if a Nutronix response is not empty */
- if (!(json.contains("message") && json["message"] == NUTRONIX_NOT_FOUND))
+ std::ostringstream message;
+ message << "Processing " << (json_bytes.size() / 100.0) << "KB from the Nutritionix API";
+ sb::Log::log(message);
+
+ /* Use the nlohmann library to parse the raw JSON byte data retrieved from a web request. */
+ nlohmann::json json = nlohmann::json::parse(json_bytes);
+ std::stringstream json_formatted;
+ json_formatted << std::setw(4) << json << std::endl;
+ sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
+
+ /* test that should determine if a Nutritionix response is not empty */
+ if (!(json.contains("message") && json["message"] == NUTRITIONIX_NOT_FOUND))
{
nlohmann::json food = json["foods"][0];
+ std::ostringstream message;
if (food.contains("photo") && food["photo"].value("thumb", "") != "")
{
- std::string url = food["photo"]["thumb"];
- sb::Log::log("adding image listed in Nutronix API at " + url);
- sb::Texture texture = texture_from_image_url(url);
- if (texture.generated())
- {
- item.texture(texture, url);
- }
+ std::string image_url = food["photo"]["thumb"];
+ message << "Found image URL for item " << incoming_item << " from Nutritionix at " << image_url;
+ web_get_bytes(image_url, std::bind(&Pudding::store_web_image, this, std::placeholders::_1, std::placeholders::_2));
}
- item.brand_name(food.value("brand_name", ""));
- item.product_name(food.value("food_name", ""));
- save_item_json(json, item, "Nutronix_API");
+ else
+ {
+ message << "No images found at Nutritionix for " << incoming_item;
+ }
+ sb::Log::log(message);
+ incoming_item.brand_name(food.value("brand_name", ""));
+ incoming_item.product_name(food.value("food_name", ""));
+ save_item_json(json, incoming_item, "Nutritionix_API");
}
else
{
- sb::Log::log("no results from Nutronix");
+ sb::Log::log("no results from Nutritionix");
}
}
-/* Submit a query to Edamam API and insert relevant results into supplied Item object
- */
-void Pudding::incorporate_edamam_api(Item& item)
+void Pudding::incorporate_edamam_api(const std::vector& json_bytes, const std::string& url)
{
- sb::Log::log("checking Edamam API");
- /* build API url by concatenating relevant values into query string */
- std::stringstream url;
- url << "https://api.edamam.com/api/food-database/v2/parser?upc=" << item.upc() << "&app_id=" <<
- configuration()["api"]["edamam-app-id"].get() << "&app_key=" <<
- configuration()["api"]["edamam-app-key"].get();
- nlohmann::json json = json_from_url(url.str());
+ std::ostringstream message;
+ message << "Processing " << (json_bytes.size() / 100.0) << "KB from the Edamam API";
+ sb::Log::log(message);
+
+ /* Use the nlohmann library to parse the raw JSON byte data retrieved from a web request. */
+ nlohmann::json json = nlohmann::json::parse(json_bytes);
+ std::stringstream json_formatted;
+ json_formatted << std::setw(4) << json << std::endl;
+ sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
+
/* test that should determine if a Edamam response has food data */
if (json.contains("hints") && json["hints"][0].contains("food"))
{
nlohmann::json food = json["hints"][0]["food"];
+ std::ostringstream message;
if (food.value("image", "") != "")
{
- std::string url = food["image"];
- sb::Texture texture = texture_from_image_url(url);
- if (texture.generated())
- {
- item.texture(texture, url);
- }
- item.product_name(food.value("label", ""));
+ std::string image_url = food["image"];
+ message << "Found URL to image for item " << incoming_item << " from Edamam at " << image_url;
+ web_get_bytes(image_url, std::bind(&Pudding::store_web_image, this, std::placeholders::_1, std::placeholders::_2));
}
- save_item_json(json, item, "Edamam_API");
+ else
+ {
+ message << "No images found at Edamam for " << incoming_item;
+ }
+ sb::Log::log(message);
+ incoming_item.product_name(food.value("label", ""));
+ save_item_json(json, incoming_item, "Edamam_API");
}
else
{
@@ -641,16 +650,18 @@ void Pudding::incorporate_edamam_api(Item& item)
}
}
-/* Submit a query to the Best Buy API and insert relevant results into supplied Item object
- */
-void Pudding::incorporate_best_buy_api(Item& item)
+void Pudding::incorporate_best_buy_api(const std::vector& json_bytes, const std::string& url)
{
- sb::Log::log("checking Best Buy API");
- /* build API url by concatenating relevant values into query string */
- std::stringstream url;
- url << "https://api.bestbuy.com/v1/products(upc=" << item.upc() << ")?format=json&apiKey=" <<
- configuration()["api"]["best-buy-api-key"].get();
- nlohmann::json json = json_from_url(url.str());
+ std::ostringstream message;
+ message << "Processing " << (json_bytes.size() / 100.0) << "KB from the Best Buy API";
+ sb::Log::log(message);
+
+ /* Use the nlohmann library to parse the raw JSON byte data retrieved from a web request. */
+ nlohmann::json json = nlohmann::json::parse(json_bytes);
+ std::stringstream json_formatted;
+ json_formatted << std::setw(4) << json << std::endl;
+ sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
+
/* test that should determine if a Best Buy response has a result */
if (json.contains("total") && json["total"].get() > 0)
{
@@ -658,18 +669,21 @@ void Pudding::incorporate_best_buy_api(Item& item)
/* look up image (for games this is box art) and "alternate views image" (for games this is a screen shot) */
for (std::string key : {"alternateViewsImage", "image"})
{
+ std::ostringstream message;
if (product.value(key, "") != "")
{
- std::string url = product[key];
- sb::Texture texture = texture_from_image_url(url);
- if (texture.generated())
- {
- item.texture(texture, url);
- }
+ std::string image_url = product[key];
+ message << "Found URL to image for item " << incoming_item << " from Best Buy at " << image_url;
+ web_get_bytes(image_url, std::bind(&Pudding::store_web_image, this, std::placeholders::_1, std::placeholders::_2));
}
+ else
+ {
+ message << "No images found at Best Buy for " << incoming_item;
+ }
+ sb::Log::log(message);
}
- item.product_name(product.value("name", ""));
- save_item_json(json, item, "Best_Buy_API");
+ incoming_item.product_name(product.value("name", ""));
+ save_item_json(json, incoming_item, "Best_Buy_API");
}
else
{
@@ -677,32 +691,45 @@ void Pudding::incorporate_best_buy_api(Item& item)
}
}
-/* Look for item upc in the Google Books API and use the result to fill out item properties if found. */
-void Pudding::incorporate_google_books_api(Item& item)
+void Pudding::incorporate_google_books_api(const std::vector& json_bytes, const std::string& url)
{
- sb::Log::log("checking Google Books API");
- nlohmann::json json = json_from_url(GOOGLE_BOOKS_API_URL + item.upc());
+ std::ostringstream message;
+ message << "Processing " << (json_bytes.size() / 100.0) << "KB from the Google Books API";
+ sb::Log::log(message);
+
+ /* Use the nlohmann library to parse the raw JSON byte data retrieved from a web request. */
+ nlohmann::json json = nlohmann::json::parse(json_bytes);
+ std::stringstream json_formatted;
+ json_formatted << std::setw(4) << json << std::endl;
+ sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
+
/* test that should determine if a Google Books API response is not empty */
if (json.value("totalItems", 0) > 0 && json.contains("items") && json["items"][0].contains("volumeInfo"))
{
/* book specific section of the JSON */
json = json["items"][0]["volumeInfo"];
+
/* get the image data */
+ std::ostringstream message;
if (json.contains("imageLinks") && json["imageLinks"].value("thumbnail", "") != "")
{
std::string image_url = json["imageLinks"]["thumbnail"];
- sb::Texture texture = texture_from_image_url(image_url);
- if (texture.generated())
- {
- item.texture(texture, image_url);
- }
+ message << "Found URL to image for item " << incoming_item << " from Google Books at " << image_url;
+ web_get_bytes(image_url, std::bind(&Pudding::store_web_image, this, std::placeholders::_1, std::placeholders::_2));
}
+ else
+ {
+ message << "No images found at Google Books for " << incoming_item;
+ }
+ sb::Log::log(message);
+
if (json.contains("authors"))
{
- item.brand_name(json["authors"][0]);
+ incoming_item.brand_name(json["authors"][0]);
}
- item.product_name(json.value("title", ""));
- save_item_json(json, item, "Google_Books_API");
+
+ incoming_item.product_name(json.value("title", ""));
+ save_item_json(json, incoming_item, "Google_Books_API");
}
else
{
@@ -743,44 +770,51 @@ void Pudding::save_item_json(const nlohmann::json& json, const Item& item, const
}
}
-/* Download the JSON data at the submitted URL, and return it as a JSON object
- */
-nlohmann::json Pudding::json_from_url(const std::string& url, const std::vector& headers)
+void Pudding::web_get_bytes(std::string url, const web_callback& callback, const std::vector& headers)
{
- std::vector storage;
- web_get_bytes(url, storage, headers);
- nlohmann::json json = nlohmann::json::parse(storage);
- std::stringstream json_formatted;
- json_formatted << std::setw(4) << json << std::endl;
- sb::Log::log(json_formatted.str(), sb::Log::DEBUG);
- return json;
-}
+ std::stringstream message;
+ message << "Fetching data from " << url;
+ sb::Log::log(message.str());
+
+ /* Add a request object to the end of the vector of launched requests. */
+ Request* request = new Request(callback, url);
+ requests.push_back(request);
+
+ /* Use the CORS anywhere proxy */
+ url = "https://mario.shampoo.ooo:8088/" + url;
-/*!
- * Store the bytes retrieved from `url` in the byte vector `storage`.
- *
- * The compiler will determine whether to use cURL or the Emscripten Fetch API to do the retrieval, depending on whether it is compiling for
- * Emscripten.
- *
- * The optional `headers` parameter will be added to the request when using cURL, but not when using the Emscripten Fetch API.
- *
- * @param url URL containing data to be retrieved
- * @param storage A reference to a vector of bytes which will be filled with the data retrieved from the URL
- * @param headers A reference to a vector of strings that should be passed as headers with the request. It is only supported by the cURL version.
- */
-void Pudding::web_get_bytes(const std::string& url, std::vector& storage, const std::vector& headers) const
-{
#if defined(__EMSCRIPTEN__)
- /* Create a fetch attributes object. Set a callback that will be called when response data is received. Pass along the user
- * storage location to be filled by the callback. */
+ /* Create a fetch attributes object. Set the callback that will be called when response data is received. Attach the user
+ * submitted callback to the userData attribute. Set the headers. */
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
attr.onsuccess = fetch_success;
attr.onerror = fetch_error;
- attr.userData = &storage;
+ attr.userData = request;
+
+ /* Copy headers into a vector of C strings with null terminator for Emscripten */
+ if (!headers.empty())
+ {
+ std::vector* emscripten_formatted_headers = new std::vector();
+ for (const std::string& component : headers)
+ {
+ const std::string* component_c = new std::string(component.c_str());
+ emscripten_formatted_headers->push_back(component_c->c_str());
+ }
+ emscripten_formatted_headers->push_back(nullptr);
+ std::ostringstream message;
+ message << "Headers are";
+ for (const char* component : *emscripten_formatted_headers)
+ {
+ message << " " << component;
+ }
+ sb::Log::log(message);
+ attr.requestHeaders = emscripten_formatted_headers->data();
+ }
+
emscripten_fetch(&attr, url.c_str());
#else
@@ -790,7 +824,7 @@ void Pudding::web_get_bytes(const std::string& url, std::vector& s
result = curl_global_init(CURL_GLOBAL_DEFAULT);
if (result != CURLE_OK)
{
- std::cout << "curl initialization failed " << curl_easy_strerror(result) << std::endl;
+ std::cout << "cURL initialization failed " << curl_easy_strerror(result) << std::endl;
}
else
{
@@ -798,105 +832,119 @@ void Pudding::web_get_bytes(const std::string& url, std::vector& s
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Pudding::curl_write_response);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &storage);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_response);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, request);
curl_easy_setopt(curl, CURLOPT_USERAGENT, configuration()["api"]["user-agent"].get().c_str());
+
+ /* Pass submitted headers to cURL */
struct curl_slist* list = nullptr;
if (headers.size() > 0)
{
- for (const std::string& header : headers)
+ /* cURL expects headers as a list of "name: value" pair strings, so combine every two components of the headers list
+ * into a single string */
+ for (std::size_t ii = 0; ii < headers.size(); ii += 2)
{
- list = curl_slist_append(list, header.c_str());
+ std::string pair = headers[ii] + ": " + headers[ii + 1];
+ list = curl_slist_append(list, pair.c_str());
}
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+
result = curl_easy_perform(curl);
curl_slist_free_all(list);
+
if (result != CURLE_OK)
{
- std::cout << "curl request failed " << curl_easy_strerror(result) << std::endl;
+ std::cout << "cURL request failed " << curl_easy_strerror(result) << std::endl;
}
}
else
{
- std::cout << "curl initialization failed" << std::endl;
+ std::cout << "cURL initialization failed" << std::endl;
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
+ /* Call the user supplied callback */
+ request->respond();
+
#endif
+
}
#if defined(__EMSCRIPTEN__)
-/*!
- * This will be called automatically when request data is sucessfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
- * Response bytes will be inserted into the user supplied `std::vector&` at `fetch->userData`.
- */
void Pudding::fetch_success(emscripten_fetch_t* fetch)
{
- std::vector* storage = reinterpret_cast*>(fetch->userData);
- storage->insert(storage->end(), fetch->data, fetch->data + fetch->numBytes);
- std::stringstream message;
- message << "Stored " << (fetch->numBytes / 100) << "KB of image data in memory from " << fetch->url;
- sb::Log::log(message.str());
+ std::stringstream bytes_message;
+ bytes_message << "Found " << fetch->numBytes << " bytes using Emscripten Fetch API";
+ sb::Log::log(bytes_message.str());
+
+ /* Store the bytes in the request object */
+ Request* request = reinterpret_cast(fetch->userData);
+ request->store(reinterpret_cast(fetch->data), fetch->numBytes);
+
+ /* Call the user supplied callback */
+ request->respond();
+
emscripten_fetch_close(fetch);
}
-/*!
- * This will be called automatically when request data is not successfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
- */
void Pudding::fetch_error(emscripten_fetch_t* fetch)
{
- std::stringstream message;
- message << "Downloading image from " << fetch->url << " failed with status code " << fetch->status;
- sb::Log::log(message.str());
+ std::ostringstream message;
+ message << "Failed fetching " << fetch->url << " with status code " << fetch->status;
+ sb::Log::log(message);
+
+ /* Since the request failed, mark it finished */
+ Request* request = reinterpret_cast(fetch->userData);
+ request->mark_finished();
+
emscripten_fetch_close(fetch);
}
#else
-/*!
- * This will be called by cURL when it has received a buffer of data. The data will be inserted into the vector at `storage`
- *
- * @param buffer pointer to data
- * @param size size in bytes of each value
- * @param count number of values
- * @param storage pointer to a vector of unsigned 8-bit values where the data will be copied to
- * @return number of bytes copied
- */
-size_t Pudding::curl_write_response(std::uint8_t* buffer, size_t size, size_t count, std::vector* storage)
+std::size_t Pudding::curl_write_response(std::uint8_t* buffer, std::size_t size, std::size_t count, Request* request)
{
- size_t total_size = size * count;
- storage->insert(storage->end(), buffer, buffer + total_size);
- return total_size;
+ std::size_t packet_size = size * count;
+
+ std::stringstream bytes_message;
+ bytes_message << "Found " << packet_size << " bytes using cURL ";
+ sb::Log::log(bytes_message.str());
+
+ /* Store the bytes in the request object */
+ request->store(buffer, packet_size);
+
+ return packet_size;
}
#endif
-/* Allocate storage for a texture, copy the cURL response data into the storage, and return the ID that corresponds to the GL texture
- */
-sb::Texture Pudding::texture_from_image_url(const std::string& url) const
+void Pudding::store_web_image(const std::vector& image, const std::string& url)
{
- /* this texture will be returned whether we load pixels into it or not */
+ /* Get a Texture by passing the bytes through an RW ops which will enable the Texture object to load a Surface */
sb::Texture texture;
- sb::Log::log("looking up image at " + url);
- std::vector storage;
- web_get_bytes(url, storage);
- if (!storage.empty())
+ SDL_RWops* rw = SDL_RWFromConstMem(image.data(), image.size());
+ texture.load(rw);
+ SDL_RWclose(rw);
+ std::ostringstream message;
+ sb::Log::Level message_level;
+ if (texture.generated())
{
- sb::Log::log("received image data", sb::Log::DEBUG);
- /* get a Texture by passing the bytes through an RW ops which will enable the Texture object to load a Surface */
- SDL_RWops* rw = SDL_RWFromConstMem(storage.data(), storage.size());
- texture.load(rw);
- SDL_RWclose(rw);
+ message << "Loaded an image from " << url << " and attached it to " << incoming_item << " at " << &incoming_item;
+ message_level = sb::Log::INFO;
+
+ /* Use the URL as the name for the texture */
+ incoming_item.texture(texture, url);
}
else
{
- SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "image url returned no data");
+ message << "Could not generate texture from " << url;
+ message_level = sb::Log::WARN;
}
- return texture;
+ sb::Log::log(message, message_level);
}
/* Call GL's delete texture function, and print a debug statement for testing. This is defined as a static member
@@ -973,6 +1021,14 @@ void Pudding::capture_frame()
if (!camera_frame.empty())
{
+ /* Brightness and contrast adjustment, see https://docs.opencv.org/2.4.13.7/doc/tutorials/core/basic_linear_transform/basic_linear_transform.html */
+ int brightness = configuration()["scan"]["brightness-addition"];
+ float contrast = configuration()["scan"]["contrast-multiplication"];
+ if (brightness != 0 || contrast != 1.0)
+ {
+ camera_frame.convertTo(camera_frame, -1, contrast, brightness);
+ }
+
/* Finished loading into `cv::Mat`, so it is new data that is safe to read. */
new_frame_available = true;
}
@@ -1006,20 +1062,31 @@ void Pudding::update()
if (new_frame_available)
{
- sb::Log::log("Hello, World!");
#ifdef __EMSCRIPTEN__
/* Emscripten builds load pixel data into cv::Mat synchronously */
capture_frame();
+
+ /* Pixels from Emscripten are RGBA */
+ GLenum pixel_format = GL_RGBA;
+
+ /* The cv::Mat rows vs. cols (width vs. height) are correct in Emscripten? */
+ int camera_frame_width = camera_frame.size[0];
+ int camera_frame_height = camera_frame.size[1];
+#else
+ /* Pixels from cv::VideoCapture are BGR */
+ GLenum pixel_format = GL_BGR;
+
+ /* The cv::Mat rows vs. cols (width vs. height) values are swapped? */
+ int camera_frame_width = camera_frame.size[1];
+ int camera_frame_height = camera_frame.size[0];
#endif
camera_view.texture().bind();
/* Fill camera view texture memory with last frame's pixels */
- // camera_view.texture().load(camera_frame.ptr(), {camera_frame.cols, camera_frame.rows}, GL_BGR, GL_UNSIGNED_BYTE);
- // std::cout << camera_frame.size[0] << " " << camera_frame.size[1] << std::endl;
- camera_view.texture().load(camera_frame.ptr(), {320, 240}, GL_RGBA, GL_UNSIGNED_BYTE);
+ camera_view.texture().load(camera_frame.ptr(), {camera_frame_width, camera_frame_height}, pixel_format, GL_UNSIGNED_BYTE);
/* Frame data has been loaded, so there is not a new frame available anymore. */
new_frame_available = false;
- /* Convert to grayscale for ZBar */
+ /* Convert to grayscale, for ZBar */
cv::cvtColor(camera_frame, camera_frame, cv::COLOR_BGR2GRAY);
if (configuration()["scan"]["enabled"])
{
@@ -1090,15 +1157,20 @@ void Pudding::update()
/* disable bg attributes and enable pudding attributes */
background.disable();
pudding_model.attributes("position")->enable();
+ GLenum side_mode, top_mode;
if (items.size() == 0)
{
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// pudding_model.attributes("color")->enable();
+ side_mode = GL_LINES;
+ top_mode = GL_LINES;
}
else
{
// glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// pudding_model.attributes("color")->enable();
+ side_mode = GL_TRIANGLES;
+ top_mode = GL_TRIANGLE_FAN;
pudding_model.attributes("uv")->enable();
glUniform1i(uniform["mvp"]["pudding texture"], 0);
glActiveTexture(GL_TEXTURE0);
@@ -1107,14 +1179,14 @@ void Pudding::update()
/* draw pudding model */
glEnable(GL_DEPTH_TEST);
/* draw the sides of the pudding */
- glDrawArrays(GL_TRIANGLES, 0, pudding_triangle_vertex_count);
+ glDrawArrays(side_mode, 0, pudding_triangle_vertex_count);
sb::Log::gl_errors("after pudding sides, before pudding top/bottom");
/* enable squircling and draw the top and bottom of pudding */
glUniform1i(uniform["mvp"]["uv transformation"], UV_SQUIRCLE);
glUniform1f(uniform["mvp"]["coordinate bound"], configuration()["pudding"]["top-radius"]);
- glDrawArrays(GL_TRIANGLE_FAN, pudding_triangle_vertex_count, pudding_fan_vertex_count);
+ glDrawArrays(top_mode, pudding_triangle_vertex_count, pudding_fan_vertex_count);
glUniform1f(uniform["mvp"]["coordinate bound"], configuration()["pudding"]["base-radius"]);
- glDrawArrays(GL_TRIANGLE_FAN, pudding_triangle_vertex_count + pudding_fan_vertex_count, pudding_fan_vertex_count);
+ glDrawArrays(top_mode, pudding_triangle_vertex_count + pudding_fan_vertex_count, pudding_fan_vertex_count);
/* disable squircling for all other drawing */
glUniform1i(uniform["mvp"]["uv transformation"], UV_NONE);
/* regular fill mode enabled for all other drawing */
@@ -1182,12 +1254,87 @@ void Pudding::update()
}
SDL_GL_SwapWindow(window());
sb::Log::gl_errors("at end of update");
- /* add a new item if a new barcode was scanned or entered */
- if (current_barcode != previous_barcode)
+
+ /* Launch requests if a new barcode was scanned or entered */
+ if (camera_switch && current_barcode != previous_barcode)
{
add_item(current_barcode);
previous_barcode = current_barcode;
}
+
+ /* Delete and erase finished requests from the vector using iterators to erase while reading the vector */
+ for (auto iter = requests.begin(); iter != requests.end();)
+ {
+ if ((*iter)->finished())
+ {
+ std::ostringstream message;
+ message << "Freeing and removing request object for " << (*iter)->url();
+ sb::Log::log(message);
+
+ /* Free the heap allocated Request */
+ delete *iter;
+
+ /* Get a iterator that points to the next request, which may have been moved after erase */
+ iter = requests.erase(iter);
+ }
+ else
+ {
+ /* Only increment the iterator when there was no erase */
+ iter++;
+ }
+ }
+
+ /* If requests are finished processing and the incoming item has a texture, add the item to the item list and create a new incoming item. */
+ if (requests.empty() && incoming_item.texture_count() > 0)
+ {
+ std::ostringstream message;
+ message << "Adding item " << incoming_item.full_name() << " to inventory";
+ sb::Log::log(message);
+
+ items.push_back(incoming_item);
+
+ /* Set item index to end so newest item will display. */
+ item_carousel.end(items);
+
+ /* Move the camera button away from center to make room for inventory button if this is the first item added. */
+ if (items.size() == 1)
+ {
+ const nlohmann::json& interface = configuration()["interface"];
+ camera_button.translation({-1.0f * interface["main-button-double-x"].get(), interface["main-button-y"]});
+ }
+ incoming_item = Item();
+ }
+}
+
+Request::Request(const web_callback& callback, const std::string& url) : callback(callback), request_url(url) {}
+
+void Request::store(const std::uint8_t* buffer, const std::size_t& size)
+{
+ response.insert(response.end(), buffer, buffer + size);
+ std::stringstream store_message;
+ store_message << "Have " << (response.size() / 100.0) << "KB of data in memory";
+ sb::Log::log(store_message.str());
+}
+
+const std::string& Request::url() const
+{
+ return request_url;
+}
+
+void Request::respond()
+{
+ callback(response, url());
+ mark_finished();
+}
+
+void Request::mark_finished()
+{
+ is_finished = true;
+}
+
+const bool& Request::finished() const
+{
+ return is_finished;
}
/* Construct a Pad using a texture, a translation, a scale, and a callback function. A Pad is a Plane which can be clicked
diff --git a/src/Pudding.hpp b/src/Pudding.hpp
index 1fb4756..64ae2a7 100644
--- a/src/Pudding.hpp
+++ b/src/Pudding.hpp
@@ -315,6 +315,68 @@ void flag_frame();
void set_heap_offset(int offset);
#endif
+/*!
+ * Type declaration of a function that will accept a vector of bytes for the response data from a web request and a string for the URL.
+ */
+using web_callback = std::function&, const std::string&)>;
+
+/*!
+ * Store the state, response data, and response function of a request for web data sent to either cURL or Emscripten.
+ */
+class Request
+{
+private:
+
+ web_callback callback = nullptr;
+ std::vector response;
+ bool is_finished = false;
+ std::string request_url;
+
+public:
+
+ /*!
+ * Construct a request object, specifying a callback that will be passed the complete data in bytes. The callback must therefore accept a
+ * vector of bytes. The URL of the request can be stored.
+ *
+ * @param callback A function object that accepts a vector of bytes.
+ * @param url URL of the request
+ */
+ Request(const web_callback& callback, const std::string& url = "");
+
+ /*!
+ * Get the URL of the request if it has been specified.
+ *
+ * @return The URL of the request or an empty string if the URL was not set
+ */
+ const std::string& url() const;
+
+ /*!
+ * Add the bytes pointed to by buffer to the storage vector.
+ */
+ void store(const std::uint8_t* buffer, const std::size_t& size);
+
+ /*!
+ * Call the user supplied callback and set state to finished.
+ */
+ void respond();
+
+ /*!
+ * Set the finished state to true.
+ */
+ void mark_finished();
+
+ /*!
+ * Check if the request is complete, meaning the data has been stored in memory and the callback has run.
+ *
+ * @return true if complete, false otherwise
+ */
+ const bool& finished() const;
+
+};
+
+/*!
+ * The main game object. There is currently only support for one of these to exist at a time.
+ */
class Pudding : public Game
{
@@ -343,11 +405,11 @@ private:
/* Constants */
inline static const std::string OPEN_FOOD_API_URL = "https://world.openfoodfacts.org/api/v0/product/";
inline static const std::string OPEN_PRODUCTS_API_URL = "https://world.openproductsfacts.org/api/v0/product/";
- inline static const std::string NUTRONIX_API_URL = "https://trackapi.nutritionix.com/v2/search/item?upc=";
+ inline static const std::string NUTRITIONIX_API_URL = "https://trackapi.nutritionix.com/v2/search/item?upc=";
inline static const std::string BARCODE_MONSTER_API_URL = "https://barcode.monster/api/";
inline static const std::string BEST_BUY_API_URL_1 = "https://api.bestbuy.com/v1/products(upc=";
inline static const std::string BEST_BUY_API_URL_2 = ")?format=json&apiKey=";
- inline static const std::string NUTRONIX_NOT_FOUND = "resource not found";
+ inline static const std::string NUTRITIONIX_NOT_FOUND = "resource not found";
inline static const std::string GOOGLE_BOOKS_API_URL = "https://www.googleapis.com/books/v1/volumes?q=isbn:";
inline static const std::string GIANTBOMB_API_URL = "https://www.giantbomb.com/api/release/?api_key=";
inline static const glm::vec3 ZERO_VECTOR_3D {0, 0, 0};
@@ -360,6 +422,7 @@ private:
std::shared_ptr poke;
std::string current_barcode, previous_barcode, current_config_barcode, current_camera_barcode;
std::vector- items;
+ Item incoming_item;
Carousel item_carousel;
int effect_id = EFFECT_NONE, pudding_triangle_vertex_count = 0, pudding_fan_vertex_count = 0;
#ifndef __EMSCRIPTEN__
@@ -379,12 +442,18 @@ private:
std::map labels;
Pad camera_button, previous_button, next_button, inventory_button;
Box viewport, main_viewport, pop_up_viewport;
- std::mutex camera_mutex;
+ std::vector requests;
void load_pudding_model(float, float, int, int = 1, float = -1.0f, float = 1.0f, float = 0.3f);
void load_gl_context();
void load_tiles();
void load_pads();
+
+ /*!
+ * Try to create cv::VideoCapture object using device ID #0. If successful, this will also create a GL texture ID and
+ * storage for the camera frame on the GPU, so it must be called after GL context has been created. Create and detach
+ * a thread which will continuously read frame data.
+ */
void open_camera();
/*!
@@ -392,32 +461,118 @@ private:
*/
void close_camera();
- void incorporate_open_api(Item&, const std::string&);
- void incorporate_nutronix_api(Item&);
- void incorporate_edamam_api(Item&);
- void incorporate_best_buy_api(Item&);
- void incorporate_google_books_api(Item&);
+ /*!
+ * Check the response from Open Food/Products API and use the result to fill out item properties if found. Request the image
+ * data if an image URL is found.
+ *
+ * @param storage JSON as raw bytes fetched from the web, written to a vector
+ * @param url URL of the request
+ */
+ void incorporate_open_api(const std::vector& json_bytes, const std::string& url);
+
+ /*!
+ * Check the response from Nutritionix API and use the result to fill out item properties if found. Request the image data if an
+ * image URL is found.
+ *
+ * @param storage JSON as raw bytes fetched from the web, written to a vector
+ * @param url URL of the request
+ */
+ void incorporate_nutritionix_api(const std::vector& json_bytes, const std::string& url);
+
+ /*!
+ * Check the response from Edamame API and use the result to fill out item properties if found. Request the image data if an
+ * image URL is found.
+ *
+ * @param storage JSON as raw bytes fetched from the web, written to a vector
+ * @param url URL of the request
+ */
+ void incorporate_edamam_api(const std::vector& json_bytes, const std::string& url);
+
+ /*!
+ * Check the response from Best Buy API and use the result to fill out item properties if found. Request image data if an
+ * image URL is found.
+ *
+ * @param storage JSON as raw bytes fetched from the web, written to a vector
+ * @param url URL of the request
+ */
+ void incorporate_best_buy_api(const std::vector& json_bytes, const std::string& url);
+
+ /*!
+ * Check the response from Google API and use the result to fill out item properties if found. Request image data if an
+ * image URL is found.
+ *
+ * @param storage JSON as raw bytes fetched from the web, written to a vector
+ * @param url URL of the request
+ */
+ void incorporate_google_books_api(const std::vector& json_bytes, const std::string& url);
+
void save_item_json(const nlohmann::json&, const Item&, const std::string&) const;
- nlohmann::json json_from_url(const std::string& url, const std::vector& = {});
- void web_get_bytes(const std::string& url, std::vector& storage, const std::vector& = {}) const;
- sb::Texture texture_from_image_url(const std::string&) const;
+
+ /*!
+ * Fetch data from `url` as raw bytes. The data will be copied into a vector which will be passed to a user supplied function. A request object
+ * will be added to `launched_requests`. That vector can be checked to determine when all requests are complete.
+ *
+ * The compiler will determine whether to use cURL or the Emscripten Fetch API to do the retrieval, depending on whether it is compiling for
+ * Emscripten.
+ *
+ * @param url URL containing data to be retrieved
+ * @param callback A function pointer for a function that accepts a reference to a vector of bytes and a reference to an Item. The bytes are the
+ * response data retrieved from `url`. The function can be any arbitrary code that uses the data.
+ * @param headers Request headers as a vector of strings formatted as ["name1", "value1", "name2", "value2", ...]
+ */
+ void web_get_bytes(std::string url, const web_callback& callback, const std::vector& headers = {});
+
static void destroy_texture(GLuint*);
bool item_display_active() const;
void capture_frame();
- /* Define the appropriate callbacks for URL data loaders. Either cURL by default, or Fetch if compiling for Emscripten. */
+ /*!
+ * Create a texture to store the image data and add it to the incoming item object.
+ *
+ * @param image image data as raw bytes fetched from the web, written to a vector
+ * @param url image URL which will will be the Texture's name
+ */
+ void store_web_image(const std::vector& image, const std::string& url);
+
+ /* Declare the appropriate callbacks for asynchronous web data loaders. Either cURL by default, or Fetch if compiling for Emscripten. */
#if defined(__EMSCRIPTEN__)
+
+ /*!
+ * This will be called automatically when request data is sucessfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
+ * Data will be written to a vector, and a user supplied callback will be called and passed the data. The callback requested by the
+ * caller is in fetch->userData.
+ *
+ * @param fetch an object created by Emscripten that stores parameters for accessing the downloaded data
+ */
static void fetch_success(emscripten_fetch_t* fetch);
+
+ /*!
+ * This will be called automatically when request data is not successfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
+ *
+ * @param fetch an object created by Emscripten that stores parameters related to the request
+ */
static void fetch_error(emscripten_fetch_t* fetch);
+
#else
- static size_t curl_write_response(std::uint8_t*, size_t, size_t, std::vector*);
+
+ /*!
+ * This will be called by cURL when it has received a buffer of data. The data will be stored by the object at `request`. This may
+ * be called multiple times before the entire data received from the originally submitted URL is received.
+ *
+ * @param buffer pointer to data cURL is transferring into memory
+ * @param size size in bytes of each value
+ * @param count number of values
+ * @param request pointer to a request object which will store the data which will be freed in the update loop
+ * @return number of bytes copied
+ */
+ static std::size_t curl_write_response(std::uint8_t* buffer, std::size_t size, std::size_t count, Request* request);
+
#endif
/* Open camera on connection and close on disconnection. */
Connection<> camera_switch {
std::bind(&Pudding::open_camera, this),
std::bind(&Pudding::close_camera, this)
- // [&] { capture.release(); }
};
public: