mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Add a service to announce multiplayer rooms to web service; Add the abiltiy to receive a list of all announced rooms from web service
This commit is contained in:
		
							parent
							
								
									4b8a7eb1ca
								
							
						
					
					
						commit
						0432fc17eb
					
				
					 14 changed files with 554 additions and 18 deletions
				
			
		|  | @ -164,6 +164,9 @@ void Config::ReadValues() { | |||
|         "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); | ||||
|     Settings::values.verify_endpoint_url = sdl2_config->Get( | ||||
|         "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile"); | ||||
|     Settings::values.announce_multiplayer_room_endpoint_url = | ||||
|         sdl2_config->Get("WebService", "announce_multiplayer_room_endpoint_url", | ||||
|                          "https://services.citra-emu.org/api/multiplayer/rooms"); | ||||
|     Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", ""); | ||||
|     Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", ""); | ||||
| } | ||||
|  |  | |||
|  | @ -187,6 +187,8 @@ enable_telemetry = | |||
| telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
 | ||||
| # Endpoint URL to verify the username and token | ||||
| verify_endpoint_url = https://services.citra-emu.org/api/profile
 | ||||
| # Endpoint URL for announcing public rooms | ||||
| announce_multiplayer_room_endpoint_url = https://services.citra-emu.org/api/multiplayer/rooms
 | ||||
| # Username and token for Citra Web Service | ||||
| # See https://services.citra-emu.org/ for more info
 | ||||
| citra_username = | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU | |||
| 
 | ||||
| add_library(common STATIC | ||||
|     alignment.h | ||||
|     announce_multiplayer_room.h | ||||
|     assert.h | ||||
|     bit_field.h | ||||
|     bit_set.h | ||||
|  |  | |||
							
								
								
									
										105
									
								
								src/common/announce_multiplayer_room.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/common/announce_multiplayer_room.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,105 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <functional> | ||||
| #include <future> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Common { | ||||
| struct WebResult { | ||||
|     enum Code : u32 { | ||||
|         Success, | ||||
|         InvalidURL, | ||||
|         CredentialsMissing, | ||||
|         CprError, | ||||
|         HttpError, | ||||
|         WrongContent, | ||||
|         NoWebservice, | ||||
|     }; | ||||
|     Code result_code; | ||||
|     std::string result_string; | ||||
| }; | ||||
| } // namespace Common
 | ||||
| 
 | ||||
| namespace AnnounceMultiplayerRoom { | ||||
| 
 | ||||
| using MacAddress = std::array<u8, 6>; | ||||
| 
 | ||||
| struct Room { | ||||
|     struct Member { | ||||
|         std::string name; | ||||
|         MacAddress mac_address; | ||||
|         std::string game_name; | ||||
|         u64 game_id; | ||||
|     }; | ||||
|     std::string name; | ||||
|     std::string GUID; | ||||
|     std::string owner; | ||||
|     std::string ip; | ||||
|     u16 port; | ||||
|     u32 max_player; | ||||
|     u32 net_version; | ||||
|     bool has_password; | ||||
|     std::string preferred_game; | ||||
|     u64 preferred_game_id; | ||||
| 
 | ||||
|     std::vector<Member> members; | ||||
| }; | ||||
| using RoomList = std::vector<Room>; | ||||
| 
 | ||||
| /**
 | ||||
|  * A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should | ||||
|  * implement this interface. | ||||
|  */ | ||||
| class Backend : NonCopyable { | ||||
| public: | ||||
|     virtual ~Backend() = default; | ||||
|     virtual void SetRoomInformation(const std::string& guid, const std::string& name, | ||||
|                                     const u16 port, const u32 max_player, const u32 net_version, | ||||
|                                     const bool has_password, const std::string& preferred_game, | ||||
|                                     const u64 preferred_game_id) = 0; | ||||
|     virtual void AddPlayer(const std::string& nickname, const MacAddress& mac_address, | ||||
|                            const u64 game_id, const std::string& game_name) = 0; | ||||
|     virtual std::future<Common::WebResult> Announce() = 0; | ||||
|     virtual void ClearPlayers() = 0; | ||||
|     virtual std::future<RoomList> GetRoomList(std::function<void()> func) = 0; | ||||
|     virtual void Delete() = 0; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a | ||||
|  * functional backend implementation is not available. | ||||
|  */ | ||||
| class NullBackend : public Backend { | ||||
| public: | ||||
|     ~NullBackend() = default; | ||||
|     void SetRoomInformation(const std::string& /*guid*/, const std::string& /*name*/, | ||||
|                             const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/, | ||||
|                             const bool /*has_password*/, const std::string& /*preferred_game*/, | ||||
|                             const u64 /*preferred_game_id*/) override {} | ||||
|     void AddPlayer(const std::string& /*nickname*/, const MacAddress& /*mac_address*/, | ||||
|                    const u64 /*game_id*/, const std::string& /*game_name*/) override {} | ||||
|     std::future<Common::WebResult> Announce() override { | ||||
|         return std::async(std::launch::async, []() { | ||||
|             return Common::WebResult{Common::WebResult::Code::NoWebservice, | ||||
|                                      "WebService is missing"}; | ||||
|         }); | ||||
|     } | ||||
|     void ClearPlayers() override {} | ||||
|     std::future<RoomList> GetRoomList(std::function<void()> func) override { | ||||
|         return std::async(std::launch::async, [func]() { | ||||
|             func(); | ||||
|             return RoomList{}; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     void Delete() override {} | ||||
| }; | ||||
| 
 | ||||
| } // namespace AnnounceMultiplayerRoom
 | ||||
|  | @ -1,5 +1,7 @@ | |||
| add_library(core STATIC | ||||
|     3ds.h | ||||
|     announce_multiplayer_session.cpp | ||||
|     announce_multiplayer_session.h | ||||
|     arm/arm_interface.h | ||||
|     arm/dynarmic/arm_dynarmic.cpp | ||||
|     arm/dynarmic/arm_dynarmic.h | ||||
|  |  | |||
							
								
								
									
										120
									
								
								src/core/announce_multiplayer_session.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/core/announce_multiplayer_session.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,120 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <chrono> | ||||
| #include <vector> | ||||
| #include "announce_multiplayer_session.h" | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "common/assert.h" | ||||
| #include "core/settings.h" | ||||
| #include "network/network.h" | ||||
| 
 | ||||
| #ifdef ENABLE_WEB_SERVICE | ||||
| #include "web_service/announce_room_json.h" | ||||
| #endif | ||||
| 
 | ||||
| namespace Core { | ||||
| 
 | ||||
| // Time between room is announced to web_service
 | ||||
| static constexpr std::chrono::seconds announce_time_interval(15); | ||||
| 
 | ||||
| AnnounceMultiplayerSession::AnnounceMultiplayerSession() : announce(false), finished(true) { | ||||
| #ifdef ENABLE_WEB_SERVICE | ||||
|     backend = std::make_unique<WebService::RoomJson>( | ||||
|         Settings::values.announce_multiplayer_room_endpoint_url, Settings::values.citra_username, | ||||
|         Settings::values.citra_token); | ||||
| #else | ||||
|     backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void AnnounceMultiplayerSession::Start() { | ||||
|     if (announce_multiplayer_thread) { | ||||
|         Stop(); | ||||
|     } | ||||
| 
 | ||||
|     announce_multiplayer_thread = | ||||
|         std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); | ||||
| } | ||||
| 
 | ||||
| void AnnounceMultiplayerSession::Stop() { | ||||
|     if (!announce && finished) | ||||
|         return; | ||||
|     announce = false; | ||||
|     // Detaching the loop, to not wait for the sleep to finish. The loop thread will finish soon.
 | ||||
|     if (announce_multiplayer_thread) { | ||||
|         announce_multiplayer_thread->detach(); | ||||
|         announce_multiplayer_thread.reset(); | ||||
|         backend->Delete(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<std::function<void(const Common::WebResult&)>> | ||||
| AnnounceMultiplayerSession::BindErrorCallback( | ||||
|     std::function<void(const Common::WebResult&)> function) { | ||||
|     std::lock_guard<std::mutex> lock(callback_mutex); | ||||
|     auto handle = std::make_shared<std::function<void(const Common::WebResult&)>>(function); | ||||
|     error_callbacks.insert(handle); | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| void AnnounceMultiplayerSession::UnbindErrorCallback( | ||||
|     std::shared_ptr<std::function<void(const Common::WebResult&)>> handle) { | ||||
|     std::lock_guard<std::mutex> lock(callback_mutex); | ||||
|     error_callbacks.erase(handle); | ||||
| } | ||||
| 
 | ||||
| AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { | ||||
|     Stop(); | ||||
| } | ||||
| 
 | ||||
| void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { | ||||
|     while (!finished) { | ||||
|         std::this_thread::sleep_for(announce_time_interval / 10); | ||||
|     } | ||||
|     announce = true; | ||||
|     finished = false; | ||||
|     std::future<Common::WebResult> future; | ||||
|     while (announce) { | ||||
|         if (std::shared_ptr<Network::Room> room = Network::GetRoom().lock()) { | ||||
|             if (room->GetState() == Network::Room::State::Open) { | ||||
|                 Network::RoomInformation room_information = room->GetRoomInformation(); | ||||
|                 std::vector<Network::Room::Member> memberlist = room->GetRoomMemberList(); | ||||
|                 backend->SetRoomInformation( | ||||
|                     room_information.guid, room_information.name, room_information.port, | ||||
|                     room_information.member_slots, Network::network_version, room->HasPassword(), | ||||
|                     room_information.preferred_game, room_information.preferred_game_id); | ||||
|                 backend->ClearPlayers(); | ||||
|                 for (const auto& member : memberlist) { | ||||
|                     backend->AddPlayer(member.nickname, member.mac_address, member.game_info.id, | ||||
|                                        member.game_info.name); | ||||
|                 } | ||||
|                 future = backend->Announce(); | ||||
|             } else { | ||||
|                 announce = false; | ||||
|             } | ||||
|         } else { | ||||
|             announce = false; | ||||
|         } | ||||
|         if (future.valid()) { | ||||
|             Common::WebResult result = future.get(); | ||||
|             if (result.result_code != Common::WebResult::Success) { | ||||
|                 std::lock_guard<std::mutex> lock(callback_mutex); | ||||
|                 for (auto callback : error_callbacks) { | ||||
|                     (*callback)(result); | ||||
|                 } | ||||
|                 announce = false; | ||||
|             } | ||||
|         } | ||||
|         std::this_thread::sleep_for(announce_time_interval); | ||||
|     } | ||||
|     finished = true; | ||||
| } | ||||
| 
 | ||||
| std::future<AnnounceMultiplayerRoom::RoomList> AnnounceMultiplayerSession::GetRoomList( | ||||
|     std::function<void()> func) { | ||||
|     return backend->GetRoomList(func); | ||||
| } | ||||
| 
 | ||||
| } // namespace Core
 | ||||
							
								
								
									
										71
									
								
								src/core/announce_multiplayer_session.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/core/announce_multiplayer_session.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <set> | ||||
| #include <thread> | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Core { | ||||
| 
 | ||||
| /**
 | ||||
|  * Instruments AnnounceMultiplayerRoom::Backend. | ||||
|  * Creates a thread that regularly updates the room information and submits them | ||||
|  * An async get of room information is also possible | ||||
|  */ | ||||
| class AnnounceMultiplayerSession : NonCopyable { | ||||
| public: | ||||
|     AnnounceMultiplayerSession(); | ||||
|     ~AnnounceMultiplayerSession(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Allows to bind a function that will get called if the announce encounters an error | ||||
|      * @param function The function that gets called | ||||
|      * @return A handle that can be used the unbind the function | ||||
|      */ | ||||
|     std::shared_ptr<std::function<void(const Common::WebResult&)>> BindErrorCallback( | ||||
|         std::function<void(const Common::WebResult&)> function); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Unbind a function from the error callbacks | ||||
|      * @param handle The handle for the function that should get unbind | ||||
|      */ | ||||
|     void UnbindErrorCallback(std::shared_ptr<std::function<void(const Common::WebResult&)>> handle); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Starts the announce of a room to web services | ||||
|      */ | ||||
|     void Start(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Stops the announce to web services | ||||
|      */ | ||||
|     void Stop(); | ||||
| 
 | ||||
|     /**
 | ||||
|      *  Returns a list of all room information the backend got | ||||
|      * @param func A function that gets executed when the async get finished, e.g. a signal | ||||
|      * @return a list of rooms received from the web service | ||||
|      */ | ||||
|     std::future<AnnounceMultiplayerRoom::RoomList> GetRoomList(std::function<void()> func); | ||||
| 
 | ||||
| private: | ||||
|     std::atomic<bool> announce{false}; | ||||
|     std::atomic<bool> finished{true}; | ||||
|     std::mutex callback_mutex; | ||||
|     std::set<std::shared_ptr<std::function<void(const Common::WebResult&)>>> error_callbacks; | ||||
|     std::unique_ptr<std::thread> announce_multiplayer_thread; | ||||
| 
 | ||||
|     std::unique_ptr<AnnounceMultiplayerRoom::Backend> | ||||
|         backend; ///< Backend interface that logs fields
 | ||||
| 
 | ||||
|     void AnnounceMultiplayerLoop(); | ||||
| }; | ||||
| 
 | ||||
| } // namespace Core
 | ||||
|  | @ -134,6 +134,7 @@ struct Values { | |||
|     bool enable_telemetry; | ||||
|     std::string telemetry_endpoint_url; | ||||
|     std::string verify_endpoint_url; | ||||
|     std::string announce_multiplayer_room_endpoint_url; | ||||
|     std::string citra_username; | ||||
|     std::string citra_token; | ||||
| } extern values; | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| add_library(web_service STATIC | ||||
|     announce_room_json.cpp | ||||
|     announce_room_json.h | ||||
|     telemetry_json.cpp | ||||
|     telemetry_json.h | ||||
|     verify_login.cpp | ||||
|  |  | |||
							
								
								
									
										112
									
								
								src/web_service/announce_room_json.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/web_service/announce_room_json.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <future> | ||||
| #include <json.hpp> | ||||
| #include "common/logging/log.h" | ||||
| #include "web_service/announce_room_json.h" | ||||
| #include "web_service/web_backend.h" | ||||
| 
 | ||||
| namespace AnnounceMultiplayerRoom { | ||||
| 
 | ||||
| void to_json(nlohmann::json& json, const Room::Member& member) { | ||||
|     json["name"] = member.name; | ||||
|     json["gameName"] = member.game_name; | ||||
|     json["gameId"] = member.game_id; | ||||
| } | ||||
| 
 | ||||
| void from_json(const nlohmann::json& json, Room::Member& member) { | ||||
|     member.name = json.at("name").get<std::string>(); | ||||
|     member.game_name = json.at("gameName").get<std::string>(); | ||||
|     member.game_id = json.at("gameId").get<u64>(); | ||||
| } | ||||
| 
 | ||||
| void to_json(nlohmann::json& json, const Room& room) { | ||||
|     json["id"] = room.GUID; | ||||
|     json["port"] = room.port; | ||||
|     json["name"] = room.name; | ||||
|     json["preferredGameName"] = room.preferred_game; | ||||
|     json["preferredGameId"] = room.preferred_game_id; | ||||
|     json["maxPlayers"] = room.max_player; | ||||
|     json["netVersion"] = room.net_version; | ||||
|     json["hasPassword"] = room.has_password; | ||||
|     if (room.members.size() > 0) { | ||||
|         nlohmann::json member_json = room.members; | ||||
|         json["players"] = member_json; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void from_json(const nlohmann::json& json, Room& room) { | ||||
|     room.ip = json.at("address").get<std::string>(); | ||||
|     room.name = json.at("name").get<std::string>(); | ||||
|     room.owner = json.at("owner").get<std::string>(); | ||||
|     room.port = json.at("port").get<u16>(); | ||||
|     room.preferred_game = json.at("preferredGameName").get<std::string>(); | ||||
|     room.preferred_game_id = json.at("preferredGameId").get<u64>(); | ||||
|     room.max_player = json.at("maxPlayers").get<u32>(); | ||||
|     room.net_version = json.at("netVersion").get<u32>(); | ||||
|     room.has_password = json.at("hasPassword").get<bool>(); | ||||
|     try { | ||||
|         room.members = json.at("players").get<std::vector<Room::Member>>(); | ||||
|     } catch (const nlohmann::detail::out_of_range& e) { | ||||
|         LOG_DEBUG(Network, "Out of range %s", e.what()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace AnnounceMultiplayerRoom
 | ||||
| 
 | ||||
| namespace WebService { | ||||
| 
 | ||||
| void RoomJson::SetRoomInformation(const std::string& guid, const std::string& name, const u16 port, | ||||
|                                   const u32 max_player, const u32 net_version, | ||||
|                                   const bool has_password, const std::string& preferred_game, | ||||
|                                   const u64 preferred_game_id) { | ||||
|     room.name = name; | ||||
|     room.GUID = guid; | ||||
|     room.port = port; | ||||
|     room.max_player = max_player; | ||||
|     room.net_version = net_version; | ||||
|     room.has_password = has_password; | ||||
|     room.preferred_game = preferred_game; | ||||
|     room.preferred_game_id = preferred_game_id; | ||||
| } | ||||
| void RoomJson::AddPlayer(const std::string& nickname, | ||||
|                          const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, | ||||
|                          const std::string& game_name) { | ||||
|     AnnounceMultiplayerRoom::Room::Member member; | ||||
|     member.name = nickname; | ||||
|     member.mac_address = mac_address; | ||||
|     member.game_id = game_id; | ||||
|     member.game_name = game_name; | ||||
|     room.members.push_back(member); | ||||
| } | ||||
| 
 | ||||
| std::future<Common::WebResult> RoomJson::Announce() { | ||||
|     nlohmann::json json = room; | ||||
|     return PostJson(endpoint_url, json.dump(), false, username, token); | ||||
| } | ||||
| 
 | ||||
| void RoomJson::ClearPlayers() { | ||||
|     room.members.clear(); | ||||
| } | ||||
| 
 | ||||
| std::future<AnnounceMultiplayerRoom::RoomList> RoomJson::GetRoomList(std::function<void()> func) { | ||||
|     auto DeSerialize = [func](const std::string& reply) -> AnnounceMultiplayerRoom::RoomList { | ||||
|         nlohmann::json json = nlohmann::json::parse(reply); | ||||
|         AnnounceMultiplayerRoom::RoomList room_list = | ||||
|             json.at("rooms").get<AnnounceMultiplayerRoom::RoomList>(); | ||||
|         func(); | ||||
|         return room_list; | ||||
|     }; | ||||
|     return GetJson<AnnounceMultiplayerRoom::RoomList>(DeSerialize, endpoint_url, true, username, | ||||
|                                                       token); | ||||
| } | ||||
| 
 | ||||
| void RoomJson::Delete() { | ||||
|     nlohmann::json json; | ||||
|     json["id"] = room.GUID; | ||||
|     DeleteJson(endpoint_url, json.dump(), username, token); | ||||
| } | ||||
| 
 | ||||
| } // namespace WebService
 | ||||
							
								
								
									
										42
									
								
								src/web_service/announce_room_json.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/web_service/announce_room_json.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| // Copyright 2017 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <future> | ||||
| #include <string> | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| 
 | ||||
| namespace WebService { | ||||
| 
 | ||||
| /**
 | ||||
|  * Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from | ||||
|  * JSON, and submits/gets it to/from the Citra web service | ||||
|  */ | ||||
| class RoomJson : public AnnounceMultiplayerRoom::Backend { | ||||
| public: | ||||
|     RoomJson(const std::string& endpoint_url, const std::string& username, const std::string& token) | ||||
|         : endpoint_url(endpoint_url), username(username), token(token) {} | ||||
|     ~RoomJson() = default; | ||||
|     void SetRoomInformation(const std::string& guid, const std::string& name, const u16 port, | ||||
|                             const u32 max_player, const u32 net_version, const bool has_password, | ||||
|                             const std::string& preferred_game, | ||||
|                             const u64 preferred_game_id) override; | ||||
|     void AddPlayer(const std::string& nickname, | ||||
|                    const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, | ||||
|                    const std::string& game_name) override; | ||||
|     std::future<Common::WebResult> Announce() override; | ||||
|     void ClearPlayers() override; | ||||
|     std::future<AnnounceMultiplayerRoom::RoomList> GetRoomList(std::function<void()> func) override; | ||||
|     void Delete() override; | ||||
| 
 | ||||
| private: | ||||
|     AnnounceMultiplayerRoom::Room room; | ||||
|     std::string endpoint_url; | ||||
|     std::string username; | ||||
|     std::string token; | ||||
| }; | ||||
| 
 | ||||
| } // namespace WebService
 | ||||
|  | @ -80,6 +80,9 @@ void TelemetryJson::Complete() { | |||
|     SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); | ||||
|     SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); | ||||
|     SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); | ||||
| 
 | ||||
|     // Send the telemetry async but don't handle the errors since the were written to the log
 | ||||
|     static std::future<Common::WebResult> future = | ||||
|         PostJson(endpoint_url, TopSection().dump(), true, username, token); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <cstdlib> | ||||
| #include <thread> | ||||
| #include <cpr/cpr.h> | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "web_service/web_backend.h" | ||||
| 
 | ||||
|  | @ -31,17 +32,23 @@ void Win32WSAStartup() { | |||
| #endif | ||||
| } | ||||
| 
 | ||||
| void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, | ||||
|               const std::string& username, const std::string& token) { | ||||
| std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data, | ||||
|                                         bool allow_anonymous, const std::string& username, | ||||
|                                         const std::string& token) { | ||||
|     if (url.empty()) { | ||||
|         LOG_ERROR(WebService, "URL is invalid"); | ||||
|         return; | ||||
|         return std::async(std::launch::async, []() { | ||||
|             return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"}; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     const bool are_credentials_provided{!token.empty() && !username.empty()}; | ||||
|     if (!allow_anonymous && !are_credentials_provided) { | ||||
|         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||
|         return; | ||||
|         return std::async(std::launch::async, []() { | ||||
|             return Common::WebResult{Common::WebResult::Code::CredentialsMissing, | ||||
|                                      "Credentials needed"}; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     Win32WSAStartup(); | ||||
|  | @ -60,23 +67,26 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym | |||
|     } | ||||
| 
 | ||||
|     // Post JSON asynchronously
 | ||||
|     static std::future<void> future; | ||||
|     future = cpr::PostCallback( | ||||
|     return cpr::PostCallback( | ||||
|         [](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "POST returned cpr error: %u:%s", | ||||
|                 LOG_ERROR(WebService, "POST to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return; | ||||
|                 return Common::WebResult{Common::WebResult::Code::CprError, r.error.message}; | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "POST returned error status code: %u", r.status_code); | ||||
|                 return; | ||||
|                 LOG_ERROR(WebService, "POST to %s returned error status code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return Common::WebResult{Common::WebResult::Code::HttpError, | ||||
|                                          std::to_string(r.status_code)}; | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "POST returned wrong content: %s", | ||||
|                 LOG_ERROR(WebService, "POST to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return; | ||||
|                 return Common::WebResult{Common::WebResult::Code::WrongContent, | ||||
|                                          r.header["content-type"]}; | ||||
|             } | ||||
|             return Common::WebResult{Common::WebResult::Code::Success, ""}; | ||||
|         }, | ||||
|         cpr::Url{url}, cpr::Body{data}, header); | ||||
| } | ||||
|  | @ -115,16 +125,17 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | |||
|     return cpr::GetCallback( | ||||
|         [func{std::move(func)}](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "GET returned cpr error: %u:%s", | ||||
|                 LOG_ERROR(WebService, "GET to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return func(""); | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "GET returned error code: %u", r.status_code); | ||||
|                 LOG_ERROR(WebService, "GET to %s returned error code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return func(""); | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "GET returned wrong content: %s", | ||||
|                 LOG_ERROR(WebService, "GET to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return func(""); | ||||
|             } | ||||
|  | @ -136,5 +147,52 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | |||
| template std::future<bool> GetJson(std::function<bool(const std::string&)> func, | ||||
|                                    const std::string& url, bool allow_anonymous, | ||||
|                                    const std::string& username, const std::string& token); | ||||
| template std::future<AnnounceMultiplayerRoom::RoomList> GetJson( | ||||
|     std::function<AnnounceMultiplayerRoom::RoomList(const std::string&)> func, | ||||
|     const std::string& url, bool allow_anonymous, const std::string& username, | ||||
|     const std::string& token); | ||||
| 
 | ||||
| void DeleteJson(const std::string& url, const std::string& data, const std::string& username, | ||||
|                 const std::string& token) { | ||||
|     if (url.empty()) { | ||||
|         LOG_ERROR(WebService, "URL is invalid"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (token.empty() || username.empty()) { | ||||
|         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Win32WSAStartup(); | ||||
| 
 | ||||
|     // Built request header
 | ||||
|     cpr::Header header = {{"Content-Type", "application/json"}, | ||||
|                           {"x-username", username.c_str()}, | ||||
|                           {"x-token", token.c_str()}, | ||||
|                           {"api-version", API_VERSION}}; | ||||
| 
 | ||||
|     // Delete JSON asynchronously
 | ||||
|     static std::future<void> future; | ||||
|     future = cpr::DeleteCallback( | ||||
|         [](cpr::Response r) { | ||||
|             if (r.error) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned cpr error: %u:%s", r.url.c_str(), | ||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||
|                 return; | ||||
|             } | ||||
|             if (r.status_code >= 400) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned error status code: %u", r.url.c_str(), | ||||
|                           r.status_code); | ||||
|                 return; | ||||
|             } | ||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||
|                 LOG_ERROR(WebService, "Delete to %s returned wrong content: %s", r.url.c_str(), | ||||
|                           r.header["content-type"].c_str()); | ||||
|                 return; | ||||
|             } | ||||
|         }, | ||||
|         cpr::Url{url}, cpr::Body{data}, header); | ||||
| } | ||||
| 
 | ||||
| } // namespace WebService
 | ||||
|  |  | |||
|  | @ -7,6 +7,8 @@ | |||
| #include <functional> | ||||
| #include <future> | ||||
| #include <string> | ||||
| #include <tuple> | ||||
| #include "common/announce_multiplayer_room.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace WebService { | ||||
|  | @ -18,9 +20,11 @@ namespace WebService { | |||
|  * @param allow_anonymous If true, allow anonymous unauthenticated requests. | ||||
|  * @param username Citra username to use for authentication. | ||||
|  * @param token Citra token to use for authentication. | ||||
|  * @return future with the error or result of the POST | ||||
|  */ | ||||
| void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, | ||||
|               const std::string& username = {}, const std::string& token = {}); | ||||
| std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data, | ||||
|                                         bool allow_anonymous, const std::string& username = {}, | ||||
|                                         const std::string& token = {}); | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets JSON from services.citra-emu.org. | ||||
|  | @ -36,4 +40,14 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | |||
|                        bool allow_anonymous, const std::string& username = {}, | ||||
|                        const std::string& token = {}); | ||||
| 
 | ||||
| /**
 | ||||
|  * Delete JSON to services.citra-emu.org. | ||||
|  * @param url URL of the services.citra-emu.org endpoint to post data to. | ||||
|  * @param data String of JSON data to use for the body of the DELETE request. | ||||
|  * @param username Citra username to use for authentication. | ||||
|  * @param token Citra token to use for authentication. | ||||
|  */ | ||||
| void DeleteJson(const std::string& url, const std::string& data, const std::string& username = {}, | ||||
|                 const std::string& token = {}); | ||||
| 
 | ||||
| } // namespace WebService
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue