#define ASIO_STANDALONE #include #include #include #include #include #include #include #include #include #include #include "cardset.h" #include "imagestore.h" #include "wizard.h" namespace { using socket_type = websocketpp::server; class server { public: server(const cardset& cards, const imagestore& images, int port, std::mt19937& rng) : cards_(cards), images_(images), port_(port), rng_(rng) { socket_.init_asio(); socket_.set_message_handler([this](websocketpp::connection_hdl connection, socket_type::message_ptr message) { on_message(connection, message); }); } void run() { socket_.listen(port_); socket_.start_accept(); std::thread cleanup(std::bind(&server::cleanup_thread, this)); cleanup.detach(); std::cout << "Listening on port " << port_ << "..." << std::endl; socket_.run(); } private: void on_message(websocketpp::connection_hdl connection, socket_type::message_ptr message) { nlohmann::json msgJson; try { msgJson = nlohmann::json::parse(message->get_payload()); } catch (const std::exception& ex) { std::string response = R"( { "type": "error", "msg": "Could not parse message." } )"; socket_.send(connection, response, websocketpp::frame::opcode::value::TEXT); return; } std::string cmd = msgJson["cmd"]; if (cmd == "generate") { cmd_generate(connection, msgJson["text"]); } else { std::string response = R"( { "type": "error", "msg": "No command in message." } )"; socket_.send(connection, response, websocketpp::frame::opcode::value::TEXT); return; } } void cmd_generate(websocketpp::connection_hdl connection, std::string text) { if (text.size() > 150) { std::string response = R"( { "type": "error", "msg": "Input is too long (>150 characters)." } )"; socket_.send(connection, response, websocketpp::frame::opcode::value::TEXT); return; } asio::post(std::bind(&server::generate_thread, this, connection, text)); } void generate_thread(websocketpp::connection_hdl connection, std::string text) { std::unique_ptr generator; { std::lock_guard rng_guard(rng_mutex_); generator = std::make_unique(cards_, images_, text, rng_); } try { generator->set_status_callback( [this, connection](const std::string& status) { nlohmann::json msg; msg["type"] = "status"; msg["msg"] = status; socket_.send(connection, msg.dump(), websocketpp::frame::opcode::value::TEXT); }); Magick::Image resultImage = generator->run(); Magick::Blob resultBlob; resultImage.write(&resultBlob); std::string resultBytes((const char*)resultBlob.data(), resultBlob.length()); std::string resultEncoded = base64::to_base64(resultBytes); nlohmann::json resultMsg; resultMsg["type"] = "result"; resultMsg["image"] = resultEncoded; resultMsg["msg"] = "Success!"; socket_.send(connection, resultMsg.dump(), websocketpp::frame::opcode::value::TEXT); } catch (const std::exception& ex) { nlohmann::json response; response["type"] = "error"; response["msg"] = std::string("Error generating card (") + ex.what() + ")"; socket_.send(connection, response.dump(), websocketpp::frame::opcode::value::TEXT); } } void cleanup_thread() { for (;;) { std::this_thread::sleep_for(std::chrono::hours(24)); images_.cleanup(); } } const cardset& cards_; const imagestore& images_; const int port_; std::mutex rng_mutex_; std::mt19937& rng_; socket_type socket_; }; } // namespace int main(int argc, char** argv) { Magick::InitializeMagick(nullptr); std::random_device randomDevice; std::mt19937 rng(5); // randomDevice()); if (argc != 2) { std::cout << "usage: wizard_server [configfile]" << std::endl; return -1; } std::ifstream config_file(argv[1]); nlohmann::json config_data = nlohmann::json::parse(config_file); cardset cards(config_data["cards_path"]); imagestore images(config_data["cache_path"]); int port = config_data["port"]; server app(cards, images, port, rng); app.run(); }