backend-infra-engineer: Release v0.3.3 snapshot
This commit is contained in:
@@ -14,60 +14,57 @@ CollaborationService::CollaborationService(Rom* rom)
|
||||
version_mgr_(nullptr),
|
||||
approval_mgr_(nullptr),
|
||||
client_(std::make_unique<WebSocketClient>()),
|
||||
sync_in_progress_(false) {
|
||||
}
|
||||
sync_in_progress_(false) {}
|
||||
|
||||
CollaborationService::~CollaborationService() {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::Initialize(
|
||||
const Config& config,
|
||||
RomVersionManager* version_mgr,
|
||||
const Config& config, RomVersionManager* version_mgr,
|
||||
ProposalApprovalManager* approval_mgr) {
|
||||
|
||||
config_ = config;
|
||||
version_mgr_ = version_mgr;
|
||||
approval_mgr_ = approval_mgr;
|
||||
|
||||
|
||||
if (!version_mgr_) {
|
||||
return absl::InvalidArgumentError("version_mgr cannot be null");
|
||||
}
|
||||
|
||||
|
||||
if (!approval_mgr_) {
|
||||
return absl::InvalidArgumentError("approval_mgr cannot be null");
|
||||
}
|
||||
|
||||
|
||||
// Set up network event callbacks
|
||||
client_->OnMessage("rom_sync", [this](const nlohmann::json& payload) {
|
||||
OnRomSyncReceived(payload);
|
||||
});
|
||||
|
||||
|
||||
client_->OnMessage("proposal_shared", [this](const nlohmann::json& payload) {
|
||||
OnProposalReceived(payload);
|
||||
});
|
||||
|
||||
client_->OnMessage("proposal_vote_received", [this](const nlohmann::json& payload) {
|
||||
OnProposalUpdated(payload);
|
||||
});
|
||||
|
||||
|
||||
client_->OnMessage(
|
||||
"proposal_vote_received",
|
||||
[this](const nlohmann::json& payload) { OnProposalUpdated(payload); });
|
||||
|
||||
client_->OnMessage("proposal_updated", [this](const nlohmann::json& payload) {
|
||||
OnProposalUpdated(payload);
|
||||
});
|
||||
|
||||
client_->OnMessage("participant_joined", [this](const nlohmann::json& payload) {
|
||||
OnParticipantJoined(payload);
|
||||
});
|
||||
|
||||
|
||||
client_->OnMessage(
|
||||
"participant_joined",
|
||||
[this](const nlohmann::json& payload) { OnParticipantJoined(payload); });
|
||||
|
||||
client_->OnMessage("participant_left", [this](const nlohmann::json& payload) {
|
||||
OnParticipantLeft(payload);
|
||||
});
|
||||
|
||||
|
||||
// Store initial ROM hash
|
||||
if (rom_ && rom_->is_loaded()) {
|
||||
last_sync_hash_ = version_mgr_->GetCurrentHash();
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -81,82 +78,69 @@ void CollaborationService::Disconnect() {
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::HostSession(
|
||||
const std::string& session_name,
|
||||
const std::string& username,
|
||||
bool ai_enabled) {
|
||||
|
||||
absl::Status CollaborationService::HostSession(const std::string& session_name,
|
||||
const std::string& username,
|
||||
bool ai_enabled) {
|
||||
if (!client_->IsConnected()) {
|
||||
return absl::FailedPreconditionError("Not connected to server");
|
||||
}
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Get current ROM hash
|
||||
std::string rom_hash = version_mgr_->GetCurrentHash();
|
||||
|
||||
|
||||
// Create initial safe point
|
||||
auto snapshot_result = version_mgr_->CreateSnapshot(
|
||||
"Session start",
|
||||
username,
|
||||
true // is_checkpoint
|
||||
auto snapshot_result = version_mgr_->CreateSnapshot("Session start", username,
|
||||
true // is_checkpoint
|
||||
);
|
||||
|
||||
|
||||
if (snapshot_result.ok()) {
|
||||
version_mgr_->MarkAsSafePoint(*snapshot_result);
|
||||
}
|
||||
|
||||
|
||||
// Host session on server
|
||||
auto session_result = client_->HostSession(
|
||||
session_name,
|
||||
username,
|
||||
rom_hash,
|
||||
ai_enabled
|
||||
);
|
||||
|
||||
auto session_result =
|
||||
client_->HostSession(session_name, username, rom_hash, ai_enabled);
|
||||
|
||||
if (!session_result.ok()) {
|
||||
return session_result.status();
|
||||
}
|
||||
|
||||
|
||||
last_sync_hash_ = rom_hash;
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::JoinSession(
|
||||
const std::string& session_code,
|
||||
const std::string& username) {
|
||||
|
||||
absl::Status CollaborationService::JoinSession(const std::string& session_code,
|
||||
const std::string& username) {
|
||||
if (!client_->IsConnected()) {
|
||||
return absl::FailedPreconditionError("Not connected to server");
|
||||
}
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Create backup before joining
|
||||
auto snapshot_result = version_mgr_->CreateSnapshot(
|
||||
"Before joining session",
|
||||
username,
|
||||
true
|
||||
);
|
||||
|
||||
auto snapshot_result =
|
||||
version_mgr_->CreateSnapshot("Before joining session", username, true);
|
||||
|
||||
if (snapshot_result.ok()) {
|
||||
version_mgr_->MarkAsSafePoint(*snapshot_result);
|
||||
}
|
||||
|
||||
|
||||
// Join session
|
||||
auto session_result = client_->JoinSession(session_code, username);
|
||||
|
||||
|
||||
if (!session_result.ok()) {
|
||||
return session_result.status();
|
||||
}
|
||||
|
||||
|
||||
last_sync_hash_ = version_mgr_->GetCurrentHash();
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -164,82 +148,73 @@ absl::Status CollaborationService::LeaveSession() {
|
||||
if (!client_->InSession()) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
|
||||
return client_->LeaveSession();
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::SubmitChangesAsProposal(
|
||||
const std::string& description,
|
||||
const std::string& username) {
|
||||
|
||||
const std::string& description, const std::string& username) {
|
||||
if (!client_->InSession()) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Generate diff from last sync
|
||||
std::string current_hash = version_mgr_->GetCurrentHash();
|
||||
if (current_hash == last_sync_hash_) {
|
||||
return absl::OkStatus(); // No changes to submit
|
||||
}
|
||||
|
||||
|
||||
std::string diff = GenerateDiff(last_sync_hash_, current_hash);
|
||||
|
||||
|
||||
// Create proposal data
|
||||
nlohmann::json proposal_data = {
|
||||
{"description", description},
|
||||
{"type", "rom_modification"},
|
||||
{"diff_data", diff},
|
||||
{"from_hash", last_sync_hash_},
|
||||
{"to_hash", current_hash}
|
||||
};
|
||||
|
||||
nlohmann::json proposal_data = {{"description", description},
|
||||
{"type", "rom_modification"},
|
||||
{"diff_data", diff},
|
||||
{"from_hash", last_sync_hash_},
|
||||
{"to_hash", current_hash}};
|
||||
|
||||
// Submit to server
|
||||
auto status = client_->ShareProposal(proposal_data, username);
|
||||
|
||||
|
||||
if (status.ok() && config_.require_approval_for_sync) {
|
||||
// Proposal submitted, waiting for approval
|
||||
// The actual application will happen when approved
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::ApplyRomSync(
|
||||
const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender) {
|
||||
|
||||
absl::Status CollaborationService::ApplyRomSync(const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
if (sync_in_progress_) {
|
||||
return absl::UnavailableError("Sync already in progress");
|
||||
}
|
||||
|
||||
|
||||
sync_in_progress_ = true;
|
||||
|
||||
|
||||
// Create snapshot before applying
|
||||
if (config_.create_snapshot_before_sync) {
|
||||
auto snapshot_result = version_mgr_->CreateSnapshot(
|
||||
absl::StrFormat("Before sync from %s", sender),
|
||||
"system",
|
||||
false
|
||||
);
|
||||
|
||||
absl::StrFormat("Before sync from %s", sender), "system", false);
|
||||
|
||||
if (!snapshot_result.ok()) {
|
||||
sync_in_progress_ = false;
|
||||
return absl::InternalError("Failed to create backup snapshot");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Apply the diff
|
||||
auto status = ApplyDiff(diff_data);
|
||||
|
||||
|
||||
if (status.ok()) {
|
||||
last_sync_hash_ = rom_hash;
|
||||
} else {
|
||||
@@ -251,74 +226,63 @@ absl::Status CollaborationService::ApplyRomSync(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sync_in_progress_ = false;
|
||||
return status;
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::HandleIncomingProposal(
|
||||
const std::string& proposal_id,
|
||||
const nlohmann::json& proposal_data,
|
||||
const std::string& proposal_id, const nlohmann::json& proposal_data,
|
||||
const std::string& sender) {
|
||||
|
||||
if (!approval_mgr_) {
|
||||
return absl::FailedPreconditionError("Approval manager not initialized");
|
||||
}
|
||||
|
||||
|
||||
// Submit to approval manager
|
||||
return approval_mgr_->SubmitProposal(
|
||||
proposal_id,
|
||||
sender,
|
||||
proposal_data["description"],
|
||||
proposal_data
|
||||
);
|
||||
proposal_id, sender, proposal_data["description"], proposal_data);
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
bool approved,
|
||||
const std::string& proposal_id, bool approved,
|
||||
const std::string& username) {
|
||||
|
||||
if (!client_->InSession()) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
|
||||
// Vote locally
|
||||
auto status = approval_mgr_->VoteOnProposal(proposal_id, username, approved);
|
||||
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// Send vote to server
|
||||
return client_->VoteOnProposal(proposal_id, approved, username);
|
||||
}
|
||||
|
||||
absl::Status CollaborationService::ApplyApprovedProposal(
|
||||
const std::string& proposal_id) {
|
||||
|
||||
if (!approval_mgr_->IsProposalApproved(proposal_id)) {
|
||||
return absl::FailedPreconditionError("Proposal not approved");
|
||||
}
|
||||
|
||||
|
||||
auto proposal_result = approval_mgr_->GetProposalStatus(proposal_id);
|
||||
if (!proposal_result.ok()) {
|
||||
return proposal_result.status();
|
||||
}
|
||||
|
||||
|
||||
// Apply the proposal (implementation depends on proposal type)
|
||||
// For now, just update status
|
||||
auto status = client_->UpdateProposalStatus(proposal_id, "applied");
|
||||
|
||||
|
||||
if (status.ok()) {
|
||||
// Create snapshot after applying
|
||||
version_mgr_->CreateSnapshot(
|
||||
absl::StrFormat("Applied proposal %s", proposal_id.substr(0, 8)),
|
||||
"system",
|
||||
false
|
||||
);
|
||||
"system", false);
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -340,9 +304,9 @@ void CollaborationService::OnRomSyncReceived(const nlohmann::json& payload) {
|
||||
std::string diff_data = payload["diff_data"];
|
||||
std::string rom_hash = payload["rom_hash"];
|
||||
std::string sender = payload["sender"];
|
||||
|
||||
|
||||
auto status = ApplyRomSync(diff_data, rom_hash, sender);
|
||||
|
||||
|
||||
if (!status.ok()) {
|
||||
// Log error or notify user
|
||||
}
|
||||
@@ -352,22 +316,22 @@ void CollaborationService::OnProposalReceived(const nlohmann::json& payload) {
|
||||
std::string proposal_id = payload["proposal_id"];
|
||||
nlohmann::json proposal_data = payload["proposal_data"];
|
||||
std::string sender = payload["sender"];
|
||||
|
||||
|
||||
HandleIncomingProposal(proposal_id, proposal_data, sender);
|
||||
}
|
||||
|
||||
void CollaborationService::OnProposalUpdated(const nlohmann::json& payload) {
|
||||
std::string proposal_id = payload["proposal_id"];
|
||||
|
||||
|
||||
if (payload.contains("status")) {
|
||||
std::string status = payload["status"];
|
||||
|
||||
|
||||
if (status == "approved" && approval_mgr_) {
|
||||
// Proposal was approved, consider applying it
|
||||
// This would be triggered by the host or based on voting results
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (payload.contains("votes")) {
|
||||
// Vote update received
|
||||
nlohmann::json votes = payload["votes"];
|
||||
@@ -387,21 +351,19 @@ void CollaborationService::OnParticipantLeft(const nlohmann::json& payload) {
|
||||
|
||||
// Helper functions
|
||||
|
||||
std::string CollaborationService::GenerateDiff(
|
||||
const std::string& from_hash,
|
||||
const std::string& to_hash) {
|
||||
|
||||
std::string CollaborationService::GenerateDiff(const std::string& from_hash,
|
||||
const std::string& to_hash) {
|
||||
// Simplified diff generation
|
||||
// In production, this would generate a binary diff
|
||||
// For now, just return placeholder
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
// TODO: Implement proper binary diff generation
|
||||
// This could use algorithms like bsdiff or a custom format
|
||||
|
||||
|
||||
return "diff_placeholder";
|
||||
}
|
||||
|
||||
@@ -409,10 +371,10 @@ absl::Status CollaborationService::ApplyDiff(const std::string& diff_data) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// TODO: Implement proper diff application
|
||||
// For now, just return success
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -420,18 +382,18 @@ bool CollaborationService::ShouldAutoSync() {
|
||||
if (!config_.auto_sync_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!client_->IsConnected() || !client_->InSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (sync_in_progress_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check if enough time has passed since last sync
|
||||
// (Implementation would track last sync time)
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ namespace net {
|
||||
/**
|
||||
* @class CollaborationService
|
||||
* @brief High-level service integrating version management with networking
|
||||
*
|
||||
*
|
||||
* Bridges the gap between:
|
||||
* - Local ROM version management
|
||||
* - Remote collaboration via WebSocket
|
||||
* - Proposal approval workflow
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Automatic ROM sync on changes
|
||||
* - Network-aware proposal approval
|
||||
@@ -37,124 +37,115 @@ class CollaborationService {
|
||||
bool require_approval_for_sync = true;
|
||||
bool create_snapshot_before_sync = true;
|
||||
};
|
||||
|
||||
|
||||
explicit CollaborationService(Rom* rom);
|
||||
~CollaborationService();
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the service
|
||||
*/
|
||||
absl::Status Initialize(
|
||||
const Config& config,
|
||||
RomVersionManager* version_mgr,
|
||||
ProposalApprovalManager* approval_mgr);
|
||||
|
||||
absl::Status Initialize(const Config& config, RomVersionManager* version_mgr,
|
||||
ProposalApprovalManager* approval_mgr);
|
||||
|
||||
/**
|
||||
* Connect to collaboration server
|
||||
*/
|
||||
absl::Status Connect(const std::string& host, int port = 8765);
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect from server
|
||||
*/
|
||||
void Disconnect();
|
||||
|
||||
|
||||
/**
|
||||
* Host a new session
|
||||
*/
|
||||
absl::Status HostSession(
|
||||
const std::string& session_name,
|
||||
const std::string& username,
|
||||
bool ai_enabled = true);
|
||||
|
||||
absl::Status HostSession(const std::string& session_name,
|
||||
const std::string& username, bool ai_enabled = true);
|
||||
|
||||
/**
|
||||
* Join existing session
|
||||
*/
|
||||
absl::Status JoinSession(
|
||||
const std::string& session_code,
|
||||
const std::string& username);
|
||||
|
||||
absl::Status JoinSession(const std::string& session_code,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Leave current session
|
||||
*/
|
||||
absl::Status LeaveSession();
|
||||
|
||||
|
||||
/**
|
||||
* Submit local changes as proposal
|
||||
*/
|
||||
absl::Status SubmitChangesAsProposal(
|
||||
const std::string& description,
|
||||
const std::string& username);
|
||||
|
||||
absl::Status SubmitChangesAsProposal(const std::string& description,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Apply received ROM sync
|
||||
*/
|
||||
absl::Status ApplyRomSync(
|
||||
const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status ApplyRomSync(const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Handle incoming proposal
|
||||
*/
|
||||
absl::Status HandleIncomingProposal(
|
||||
const std::string& proposal_id,
|
||||
const nlohmann::json& proposal_data,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status HandleIncomingProposal(const std::string& proposal_id,
|
||||
const nlohmann::json& proposal_data,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Vote on proposal
|
||||
*/
|
||||
absl::Status VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
bool approved,
|
||||
const std::string& username);
|
||||
|
||||
absl::Status VoteOnProposal(const std::string& proposal_id, bool approved,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Apply approved proposal
|
||||
*/
|
||||
absl::Status ApplyApprovedProposal(const std::string& proposal_id);
|
||||
|
||||
|
||||
/**
|
||||
* Get connection status
|
||||
*/
|
||||
bool IsConnected() const;
|
||||
|
||||
|
||||
/**
|
||||
* Get session info
|
||||
*/
|
||||
absl::StatusOr<SessionInfo> GetSessionInfo() const;
|
||||
|
||||
|
||||
/**
|
||||
* Get WebSocket client (for advanced usage)
|
||||
*/
|
||||
WebSocketClient* GetClient() { return client_.get(); }
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable auto-sync
|
||||
*/
|
||||
void SetAutoSync(bool enabled);
|
||||
|
||||
|
||||
private:
|
||||
Rom* rom_;
|
||||
RomVersionManager* version_mgr_;
|
||||
ProposalApprovalManager* approval_mgr_;
|
||||
std::unique_ptr<WebSocketClient> client_;
|
||||
Config config_;
|
||||
|
||||
|
||||
// Sync state
|
||||
std::string last_sync_hash_;
|
||||
bool sync_in_progress_;
|
||||
|
||||
|
||||
// Callbacks for network events
|
||||
void OnRomSyncReceived(const nlohmann::json& payload);
|
||||
void OnProposalReceived(const nlohmann::json& payload);
|
||||
void OnProposalUpdated(const nlohmann::json& payload);
|
||||
void OnParticipantJoined(const nlohmann::json& payload);
|
||||
void OnParticipantLeft(const nlohmann::json& payload);
|
||||
|
||||
|
||||
// Helper functions
|
||||
std::string GenerateDiff(const std::string& from_hash, const std::string& to_hash);
|
||||
std::string GenerateDiff(const std::string& from_hash,
|
||||
const std::string& to_hash);
|
||||
absl::Status ApplyDiff(const std::string& diff_data);
|
||||
bool ShouldAutoSync();
|
||||
};
|
||||
|
||||
@@ -29,9 +29,10 @@ target_precompile_headers(yaze_net PRIVATE
|
||||
|
||||
target_include_directories(yaze_net PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/src/lib
|
||||
${CMAKE_SOURCE_DIR}/src/lib/imgui
|
||||
${SDL2_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/ext
|
||||
${CMAKE_SOURCE_DIR}/ext/imgui
|
||||
${CMAKE_SOURCE_DIR}/ext/json/include
|
||||
${CMAKE_SOURCE_DIR}/ext/httplib
|
||||
${PROJECT_BINARY_DIR}
|
||||
)
|
||||
|
||||
@@ -39,13 +40,14 @@ target_link_libraries(yaze_net PUBLIC
|
||||
yaze_util
|
||||
yaze_common
|
||||
${ABSL_TARGETS}
|
||||
${YAZE_SDL2_TARGETS}
|
||||
)
|
||||
|
||||
# Add JSON and httplib support if enabled
|
||||
if(YAZE_WITH_JSON)
|
||||
target_include_directories(yaze_net PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/third_party/json/include
|
||||
${CMAKE_SOURCE_DIR}/third_party/httplib)
|
||||
# Link nlohmann_json which provides the include directories automatically
|
||||
target_link_libraries(yaze_net PUBLIC nlohmann_json::nlohmann_json)
|
||||
target_include_directories(yaze_net PUBLIC ${CMAKE_SOURCE_DIR}/ext/httplib)
|
||||
target_compile_definitions(yaze_net PUBLIC YAZE_WITH_JSON)
|
||||
|
||||
# Add threading support (cross-platform)
|
||||
@@ -55,18 +57,30 @@ if(YAZE_WITH_JSON)
|
||||
# Only link OpenSSL if gRPC is NOT enabled (to avoid duplicate symbol errors)
|
||||
# When gRPC is enabled, it brings its own OpenSSL which we'll use instead
|
||||
if(NOT YAZE_WITH_GRPC)
|
||||
find_package(OpenSSL QUIET)
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(yaze_net PUBLIC OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
message(STATUS " - WebSocket with SSL/TLS support enabled")
|
||||
# CRITICAL FIX: Disable OpenSSL on Windows to avoid missing header errors
|
||||
# Windows CI doesn't have OpenSSL headers properly configured
|
||||
# WebSocket will work with plain HTTP (no SSL/TLS) on Windows
|
||||
if(NOT WIN32)
|
||||
find_package(OpenSSL QUIET)
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(yaze_net PUBLIC OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
message(STATUS " - WebSocket with SSL/TLS support enabled")
|
||||
else()
|
||||
message(STATUS " - WebSocket without SSL/TLS (OpenSSL not found)")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS " - WebSocket without SSL/TLS (OpenSSL not found)")
|
||||
message(STATUS " - Windows: WebSocket using plain HTTP (no SSL) - OpenSSL headers not available in CI")
|
||||
endif()
|
||||
else()
|
||||
# When gRPC is enabled, still enable OpenSSL features but use gRPC's OpenSSL
|
||||
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
message(STATUS " - WebSocket with SSL/TLS support enabled via gRPC's OpenSSL")
|
||||
# CRITICAL: Skip on Windows - gRPC's OpenSSL headers aren't accessible in Windows CI
|
||||
if(NOT WIN32)
|
||||
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
message(STATUS " - WebSocket with SSL/TLS support enabled via gRPC's OpenSSL")
|
||||
else()
|
||||
message(STATUS " - Windows + gRPC: WebSocket using plain HTTP (no SSL) - OpenSSL headers not available")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Windows-specific socket library
|
||||
@@ -78,21 +92,7 @@ endif()
|
||||
|
||||
# Add gRPC support for ROM service
|
||||
if(YAZE_WITH_GRPC)
|
||||
target_add_protobuf(yaze_net ${PROJECT_SOURCE_DIR}/src/protos/rom_service.proto)
|
||||
|
||||
target_link_libraries(yaze_net PUBLIC
|
||||
grpc++
|
||||
grpc++_reflection
|
||||
)
|
||||
if(YAZE_PROTOBUF_TARGETS)
|
||||
target_link_libraries(yaze_net PUBLIC ${YAZE_PROTOBUF_TARGETS})
|
||||
if(MSVC AND YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
|
||||
target_link_options(yaze_net PUBLIC /WHOLEARCHIVE:$<TARGET_FILE:${_yaze_proto_target}>)
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(yaze_net PUBLIC yaze_grpc_support)
|
||||
message(STATUS " - gRPC ROM service enabled")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/net/rom_version_manager.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
// Proto namespace alias for convenience
|
||||
namespace rom_svc = ::yaze::proto;
|
||||
@@ -13,77 +13,70 @@ namespace yaze {
|
||||
|
||||
namespace net {
|
||||
|
||||
RomServiceImpl::RomServiceImpl(
|
||||
Rom* rom,
|
||||
RomVersionManager* version_manager,
|
||||
ProposalApprovalManager* approval_manager)
|
||||
RomServiceImpl::RomServiceImpl(Rom* rom, RomVersionManager* version_manager,
|
||||
ProposalApprovalManager* approval_manager)
|
||||
: rom_(rom),
|
||||
version_mgr_(version_manager),
|
||||
approval_mgr_(approval_manager) {
|
||||
}
|
||||
approval_mgr_(approval_manager) {}
|
||||
|
||||
void RomServiceImpl::SetConfig(const Config& config) {
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::ReadBytes(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::ReadBytesRequest* request,
|
||||
rom_svc::ReadBytesResponse* response) {
|
||||
|
||||
grpc::Status RomServiceImpl::ReadBytes(grpc::ServerContext* context,
|
||||
const rom_svc::ReadBytesRequest* request,
|
||||
rom_svc::ReadBytesResponse* response) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
|
||||
"ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
uint32_t address = request->address();
|
||||
uint32_t length = request->length();
|
||||
|
||||
|
||||
// Validate range
|
||||
if (address + length > rom_->size()) {
|
||||
return grpc::Status(
|
||||
grpc::StatusCode::OUT_OF_RANGE,
|
||||
absl::StrFormat("Read beyond ROM: 0x%X+%d > %d",
|
||||
address, length, rom_->size()));
|
||||
return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
|
||||
absl::StrFormat("Read beyond ROM: 0x%X+%d > %d",
|
||||
address, length, rom_->size()));
|
||||
}
|
||||
|
||||
|
||||
// Read data
|
||||
const auto* data = rom_->data() + address;
|
||||
response->set_data(data, length);
|
||||
response->set_success(true);
|
||||
|
||||
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::WriteBytes(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::WriteBytesRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::WriteBytesRequest* request,
|
||||
rom_svc::WriteBytesResponse* response) {
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
|
||||
"ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
uint32_t address = request->address();
|
||||
const std::string& data = request->data();
|
||||
|
||||
|
||||
// Validate range
|
||||
if (address + data.size() > rom_->size()) {
|
||||
return grpc::Status(
|
||||
grpc::StatusCode::OUT_OF_RANGE,
|
||||
absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d",
|
||||
address, data.size(), rom_->size()));
|
||||
return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
|
||||
absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d",
|
||||
address, data.size(), rom_->size()));
|
||||
}
|
||||
|
||||
|
||||
// Check if approval required
|
||||
if (config_.require_approval_for_writes && approval_mgr_) {
|
||||
// Create a proposal for this write
|
||||
std::string proposal_id = absl::StrFormat(
|
||||
"write_0x%X_%zu_bytes", address, data.size());
|
||||
|
||||
std::string proposal_id =
|
||||
absl::StrFormat("write_0x%X_%zu_bytes", address, data.size());
|
||||
|
||||
if (request->has_proposal_id()) {
|
||||
proposal_id = request->proposal_id();
|
||||
}
|
||||
|
||||
|
||||
// Check if proposal is approved
|
||||
auto status = approval_mgr_->GetProposalStatus(proposal_id);
|
||||
if (status != ProposalApprovalManager::ApprovalStatus::kApproved) {
|
||||
@@ -93,7 +86,7 @@ grpc::Status RomServiceImpl::WriteBytes(
|
||||
return grpc::Status::OK; // Not an error, just needs approval
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create snapshot before write
|
||||
if (version_mgr_) {
|
||||
std::string snapshot_desc = absl::StrFormat(
|
||||
@@ -103,104 +96,87 @@ grpc::Status RomServiceImpl::WriteBytes(
|
||||
response->set_snapshot_id(std::to_string(snapshot_result.value()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Perform write
|
||||
std::memcpy(rom_->mutable_data() + address, data.data(), data.size());
|
||||
|
||||
|
||||
response->set_success(true);
|
||||
response->set_message("Write successful");
|
||||
|
||||
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::GetRomInfo(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::GetRomInfoRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::GetRomInfoRequest* request,
|
||||
rom_svc::GetRomInfoResponse* response) {
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
|
||||
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
|
||||
"ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
auto* info = response->mutable_info();
|
||||
info->set_title(rom_->title());
|
||||
info->set_size(rom_->size());
|
||||
info->set_is_loaded(rom_->is_loaded());
|
||||
info->set_filename(rom_->filename());
|
||||
|
||||
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::GetTileData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::GetTileDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::GetTileDataRequest* request,
|
||||
rom_svc::GetTileDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"GetTileData not yet implemented");
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"GetTileData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::SetTileData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::SetTileDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::SetTileDataRequest* request,
|
||||
rom_svc::SetTileDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"SetTileData not yet implemented");
|
||||
"SetTileData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::GetMapData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::GetMapDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::GetMapDataRequest* request,
|
||||
rom_svc::GetMapDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"GetMapData not yet implemented");
|
||||
"GetMapData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::SetMapData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::SetMapDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::SetMapDataRequest* request,
|
||||
rom_svc::SetMapDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"SetMapData not yet implemented");
|
||||
"SetMapData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::GetSpriteData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::GetSpriteDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::GetSpriteDataRequest* request,
|
||||
rom_svc::GetSpriteDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"GetSpriteData not yet implemented");
|
||||
"GetSpriteData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::SetSpriteData(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::SetSpriteDataRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::SetSpriteDataRequest* request,
|
||||
rom_svc::SetSpriteDataResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"SetSpriteData not yet implemented");
|
||||
"SetSpriteData not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::GetDialogue(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::GetDialogueRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::GetDialogueRequest* request,
|
||||
rom_svc::GetDialogueResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"GetDialogue not yet implemented");
|
||||
"GetDialogue not yet implemented");
|
||||
}
|
||||
|
||||
grpc::Status RomServiceImpl::SetDialogue(
|
||||
grpc::ServerContext* context,
|
||||
const rom_svc::SetDialogueRequest* request,
|
||||
grpc::ServerContext* context, const rom_svc::SetDialogueRequest* request,
|
||||
rom_svc::SetDialogueResponse* response) {
|
||||
|
||||
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
|
||||
"SetDialogue not yet implemented");
|
||||
"SetDialogue not yet implemented");
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#undef ERROR
|
||||
#endif // _WIN32
|
||||
#include <grpcpp/grpcpp.h>
|
||||
|
||||
#include "protos/rom_service.grpc.pb.h"
|
||||
#ifdef _WIN32
|
||||
#pragma pop_macro("DWORD")
|
||||
@@ -23,8 +24,8 @@
|
||||
// Note: Proto files will be generated to build directory
|
||||
#endif
|
||||
|
||||
#include "app/rom.h"
|
||||
#include "app/net/rom_version_manager.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
@@ -34,13 +35,13 @@ namespace net {
|
||||
|
||||
/**
|
||||
* @brief gRPC service implementation for remote ROM manipulation
|
||||
*
|
||||
*
|
||||
* Enables remote clients (like z3ed CLI) to:
|
||||
* - Read/write ROM data
|
||||
* - Submit proposals for collaborative editing
|
||||
* - Manage ROM versions and snapshots
|
||||
* - Query ROM structures (overworld, dungeons, sprites)
|
||||
*
|
||||
*
|
||||
* Thread-safe and designed for concurrent access.
|
||||
*/
|
||||
class RomServiceImpl final : public proto::RomService::Service {
|
||||
@@ -54,120 +55,113 @@ class RomServiceImpl final : public proto::RomService::Service {
|
||||
int max_read_size_bytes = 1024 * 1024; // 1MB max per read
|
||||
bool allow_raw_rom_access = true; // Allow direct byte access
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Construct ROM service
|
||||
* @param rom Pointer to ROM instance (not owned)
|
||||
* @param version_mgr Pointer to version manager (not owned, optional)
|
||||
* @param approval_mgr Pointer to approval manager (not owned, optional)
|
||||
*/
|
||||
RomServiceImpl(Rom* rom,
|
||||
RomVersionManager* version_mgr = nullptr,
|
||||
RomServiceImpl(Rom* rom, RomVersionManager* version_mgr = nullptr,
|
||||
ProposalApprovalManager* approval_mgr = nullptr);
|
||||
|
||||
|
||||
~RomServiceImpl() override = default;
|
||||
|
||||
|
||||
// Initialize with configuration
|
||||
void SetConfig(const Config& config);
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Basic ROM Operations
|
||||
// =========================================================================
|
||||
|
||||
grpc::Status ReadBytes(
|
||||
grpc::ServerContext* context,
|
||||
const proto::ReadBytesRequest* request,
|
||||
proto::ReadBytesResponse* response) override;
|
||||
|
||||
grpc::Status WriteBytes(
|
||||
grpc::ServerContext* context,
|
||||
const proto::WriteBytesRequest* request,
|
||||
proto::WriteBytesResponse* response) override;
|
||||
|
||||
grpc::Status GetRomInfo(
|
||||
grpc::ServerContext* context,
|
||||
const proto::GetRomInfoRequest* request,
|
||||
proto::GetRomInfoResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status ReadBytes(grpc::ServerContext* context,
|
||||
const proto::ReadBytesRequest* request,
|
||||
proto::ReadBytesResponse* response) override;
|
||||
|
||||
grpc::Status WriteBytes(grpc::ServerContext* context,
|
||||
const proto::WriteBytesRequest* request,
|
||||
proto::WriteBytesResponse* response) override;
|
||||
|
||||
grpc::Status GetRomInfo(grpc::ServerContext* context,
|
||||
const proto::GetRomInfoRequest* request,
|
||||
proto::GetRomInfoResponse* response) override;
|
||||
|
||||
// =========================================================================
|
||||
// Overworld Operations
|
||||
// =========================================================================
|
||||
|
||||
|
||||
grpc::Status ReadOverworldMap(
|
||||
grpc::ServerContext* context,
|
||||
const proto::ReadOverworldMapRequest* request,
|
||||
proto::ReadOverworldMapResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status WriteOverworldTile(
|
||||
grpc::ServerContext* context,
|
||||
const proto::WriteOverworldTileRequest* request,
|
||||
proto::WriteOverworldTileResponse* response) override;
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Dungeon Operations
|
||||
// =========================================================================
|
||||
|
||||
|
||||
grpc::Status ReadDungeonRoom(
|
||||
grpc::ServerContext* context,
|
||||
const proto::ReadDungeonRoomRequest* request,
|
||||
proto::ReadDungeonRoomResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status WriteDungeonTile(
|
||||
grpc::ServerContext* context,
|
||||
const proto::WriteDungeonTileRequest* request,
|
||||
proto::WriteDungeonTileResponse* response) override;
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Sprite Operations
|
||||
// =========================================================================
|
||||
|
||||
grpc::Status ReadSprite(
|
||||
grpc::ServerContext* context,
|
||||
const proto::ReadSpriteRequest* request,
|
||||
proto::ReadSpriteResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status ReadSprite(grpc::ServerContext* context,
|
||||
const proto::ReadSpriteRequest* request,
|
||||
proto::ReadSpriteResponse* response) override;
|
||||
|
||||
// =========================================================================
|
||||
// Proposal System
|
||||
// =========================================================================
|
||||
|
||||
|
||||
grpc::Status SubmitRomProposal(
|
||||
grpc::ServerContext* context,
|
||||
const proto::SubmitRomProposalRequest* request,
|
||||
proto::SubmitRomProposalResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status GetProposalStatus(
|
||||
grpc::ServerContext* context,
|
||||
const proto::GetProposalStatusRequest* request,
|
||||
proto::GetProposalStatusResponse* response) override;
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Version Management
|
||||
// =========================================================================
|
||||
|
||||
grpc::Status CreateSnapshot(
|
||||
grpc::ServerContext* context,
|
||||
const proto::CreateSnapshotRequest* request,
|
||||
proto::CreateSnapshotResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status CreateSnapshot(grpc::ServerContext* context,
|
||||
const proto::CreateSnapshotRequest* request,
|
||||
proto::CreateSnapshotResponse* response) override;
|
||||
|
||||
grpc::Status RestoreSnapshot(
|
||||
grpc::ServerContext* context,
|
||||
const proto::RestoreSnapshotRequest* request,
|
||||
proto::RestoreSnapshotResponse* response) override;
|
||||
|
||||
grpc::Status ListSnapshots(
|
||||
grpc::ServerContext* context,
|
||||
const proto::ListSnapshotsRequest* request,
|
||||
proto::ListSnapshotsResponse* response) override;
|
||||
|
||||
|
||||
grpc::Status ListSnapshots(grpc::ServerContext* context,
|
||||
const proto::ListSnapshotsRequest* request,
|
||||
proto::ListSnapshotsResponse* response) override;
|
||||
|
||||
private:
|
||||
Config config_;
|
||||
Rom* rom_; // Not owned
|
||||
RomVersionManager* version_mgr_; // Not owned, may be null
|
||||
Rom* rom_; // Not owned
|
||||
RomVersionManager* version_mgr_; // Not owned, may be null
|
||||
ProposalApprovalManager* approval_mgr_; // Not owned, may be null
|
||||
|
||||
|
||||
// Helper to check if ROM is loaded
|
||||
grpc::Status ValidateRomLoaded();
|
||||
|
||||
|
||||
// Helper to create snapshot before write operations
|
||||
absl::Status MaybeCreateSnapshot(const std::string& description);
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
// For compression (placeholder - would use zlib or similar)
|
||||
#include <vector>
|
||||
@@ -33,13 +33,15 @@ std::string ComputeHash(const std::vector<uint8_t>& data) {
|
||||
std::string GenerateId() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
return absl::StrFormat("snap_%lld", ms);
|
||||
}
|
||||
|
||||
int64_t GetCurrentTimestamp() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -49,9 +51,7 @@ int64_t GetCurrentTimestamp() {
|
||||
// ============================================================================
|
||||
|
||||
RomVersionManager::RomVersionManager(Rom* rom)
|
||||
: rom_(rom),
|
||||
last_backup_time_(0) {
|
||||
}
|
||||
: rom_(rom), last_backup_time_(0) {}
|
||||
|
||||
RomVersionManager::~RomVersionManager() {
|
||||
// Cleanup if needed
|
||||
@@ -59,34 +59,29 @@ RomVersionManager::~RomVersionManager() {
|
||||
|
||||
absl::Status RomVersionManager::Initialize(const Config& config) {
|
||||
config_ = config;
|
||||
|
||||
|
||||
// Create initial snapshot
|
||||
auto initial_result = CreateSnapshot(
|
||||
"Initial state",
|
||||
"system",
|
||||
true);
|
||||
|
||||
auto initial_result = CreateSnapshot("Initial state", "system", true);
|
||||
|
||||
if (!initial_result.ok()) {
|
||||
return initial_result.status();
|
||||
}
|
||||
|
||||
|
||||
// Mark as safe point
|
||||
return MarkAsSafePoint(*initial_result);
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
|
||||
const std::string& description,
|
||||
const std::string& creator,
|
||||
const std::string& description, const std::string& creator,
|
||||
bool is_checkpoint) {
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Get ROM data
|
||||
std::vector<uint8_t> rom_data(rom_->size());
|
||||
std::memcpy(rom_data.data(), rom_->data(), rom_->size());
|
||||
|
||||
|
||||
// Create snapshot
|
||||
RomSnapshot snapshot;
|
||||
snapshot.snapshot_id = GenerateId();
|
||||
@@ -96,7 +91,7 @@ absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
|
||||
snapshot.creator = creator;
|
||||
snapshot.is_checkpoint = is_checkpoint;
|
||||
snapshot.is_safe_point = false;
|
||||
|
||||
|
||||
// Compress if enabled
|
||||
if (config_.compress_snapshots) {
|
||||
snapshot.rom_data = CompressData(rom_data);
|
||||
@@ -105,37 +100,39 @@ absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
|
||||
snapshot.rom_data = std::move(rom_data);
|
||||
snapshot.compressed_size = snapshot.rom_data.size();
|
||||
}
|
||||
|
||||
|
||||
#ifdef YAZE_WITH_JSON
|
||||
snapshot.metadata = nlohmann::json::object();
|
||||
snapshot.metadata["size"] = rom_->size();
|
||||
snapshot.metadata["auto_backup"] = !is_checkpoint;
|
||||
#endif
|
||||
|
||||
|
||||
// Store snapshot
|
||||
snapshots_[snapshot.snapshot_id] = std::move(snapshot);
|
||||
last_known_hash_ = snapshots_[snapshot.snapshot_id].rom_hash;
|
||||
|
||||
|
||||
// Cleanup if needed
|
||||
if (snapshots_.size() > config_.max_snapshots) {
|
||||
CleanupOldSnapshots();
|
||||
}
|
||||
|
||||
|
||||
return snapshots_[snapshot.snapshot_id].snapshot_id;
|
||||
}
|
||||
|
||||
absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id) {
|
||||
absl::Status RomVersionManager::RestoreSnapshot(
|
||||
const std::string& snapshot_id) {
|
||||
auto it = snapshots_.find(snapshot_id);
|
||||
if (it == snapshots_.end()) {
|
||||
return absl::NotFoundError(absl::StrCat("Snapshot not found: ", snapshot_id));
|
||||
return absl::NotFoundError(
|
||||
absl::StrCat("Snapshot not found: ", snapshot_id));
|
||||
}
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
const RomSnapshot& snapshot = it->second;
|
||||
|
||||
|
||||
// Decompress if needed
|
||||
std::vector<uint8_t> rom_data;
|
||||
if (config_.compress_snapshots) {
|
||||
@@ -143,55 +140,54 @@ absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id)
|
||||
} else {
|
||||
rom_data = snapshot.rom_data;
|
||||
}
|
||||
|
||||
|
||||
// Verify size matches
|
||||
if (rom_data.size() != rom_->size()) {
|
||||
return absl::DataLossError("Snapshot size mismatch");
|
||||
}
|
||||
|
||||
|
||||
// Create backup before restore
|
||||
auto backup_result = CreateSnapshot(
|
||||
"Pre-restore backup",
|
||||
"system",
|
||||
false);
|
||||
|
||||
auto backup_result = CreateSnapshot("Pre-restore backup", "system", false);
|
||||
|
||||
if (!backup_result.ok()) {
|
||||
return absl::InternalError("Failed to create pre-restore backup");
|
||||
}
|
||||
|
||||
|
||||
// Restore ROM data
|
||||
std::memcpy(rom_->mutable_data(), rom_data.data(), rom_data.size());
|
||||
|
||||
|
||||
last_known_hash_ = snapshot.rom_hash;
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status RomVersionManager::MarkAsSafePoint(const std::string& snapshot_id) {
|
||||
absl::Status RomVersionManager::MarkAsSafePoint(
|
||||
const std::string& snapshot_id) {
|
||||
auto it = snapshots_.find(snapshot_id);
|
||||
if (it == snapshots_.end()) {
|
||||
return absl::NotFoundError("Snapshot not found");
|
||||
}
|
||||
|
||||
|
||||
it->second.is_safe_point = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::vector<RomSnapshot> RomVersionManager::GetSnapshots(bool safe_points_only) const {
|
||||
std::vector<RomSnapshot> RomVersionManager::GetSnapshots(
|
||||
bool safe_points_only) const {
|
||||
std::vector<RomSnapshot> result;
|
||||
|
||||
|
||||
for (const auto& [id, snapshot] : snapshots_) {
|
||||
if (!safe_points_only || snapshot.is_safe_point) {
|
||||
result.push_back(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sort by timestamp (newest first)
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const RomSnapshot& a, const RomSnapshot& b) {
|
||||
return a.timestamp > b.timestamp;
|
||||
});
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -209,12 +205,12 @@ absl::Status RomVersionManager::DeleteSnapshot(const std::string& snapshot_id) {
|
||||
if (it == snapshots_.end()) {
|
||||
return absl::NotFoundError("Snapshot not found");
|
||||
}
|
||||
|
||||
|
||||
// Don't allow deleting safe points
|
||||
if (it->second.is_safe_point) {
|
||||
return absl::FailedPreconditionError("Cannot delete safe point");
|
||||
}
|
||||
|
||||
|
||||
snapshots_.erase(it);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -223,29 +219,29 @@ absl::StatusOr<bool> RomVersionManager::DetectCorruption() {
|
||||
if (!config_.enable_corruption_detection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Compute current hash
|
||||
std::vector<uint8_t> current_data(rom_->size());
|
||||
std::memcpy(current_data.data(), rom_->data(), rom_->size());
|
||||
std::string current_hash = ComputeHash(current_data);
|
||||
|
||||
|
||||
// Basic integrity checks
|
||||
auto integrity_status = ValidateRomIntegrity();
|
||||
if (!integrity_status.ok()) {
|
||||
return true; // Corruption detected
|
||||
}
|
||||
|
||||
|
||||
// Check against last known good hash (if modified unexpectedly)
|
||||
if (!last_known_hash_.empty() && current_hash != last_known_hash_) {
|
||||
// ROM changed without going through version manager
|
||||
// This might be intentional, so just flag it
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -255,7 +251,7 @@ absl::Status RomVersionManager::AutoRecover() {
|
||||
if (snapshots.empty()) {
|
||||
return absl::NotFoundError("No safe points available for recovery");
|
||||
}
|
||||
|
||||
|
||||
// Restore from most recent safe point
|
||||
return RestoreSnapshot(snapshots[0].snapshot_id);
|
||||
}
|
||||
@@ -264,7 +260,7 @@ std::string RomVersionManager::GetCurrentHash() const {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
std::vector<uint8_t> data(rom_->size());
|
||||
std::memcpy(data.data(), rom_->data(), rom_->size());
|
||||
return ComputeHash(data);
|
||||
@@ -273,53 +269,56 @@ std::string RomVersionManager::GetCurrentHash() const {
|
||||
absl::Status RomVersionManager::CleanupOldSnapshots() {
|
||||
// Keep safe points and checkpoints
|
||||
// Remove oldest auto-backups first
|
||||
|
||||
|
||||
std::vector<std::pair<int64_t, std::string>> auto_backups;
|
||||
for (const auto& [id, snapshot] : snapshots_) {
|
||||
if (!snapshot.is_safe_point && !snapshot.is_checkpoint) {
|
||||
auto_backups.push_back({snapshot.timestamp, id});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sort by timestamp (oldest first)
|
||||
std::sort(auto_backups.begin(), auto_backups.end());
|
||||
|
||||
|
||||
// Delete oldest until within limits
|
||||
while (snapshots_.size() > config_.max_snapshots && !auto_backups.empty()) {
|
||||
snapshots_.erase(auto_backups.front().second);
|
||||
auto_backups.erase(auto_backups.begin());
|
||||
}
|
||||
|
||||
|
||||
// Check storage limit
|
||||
while (GetTotalStorageUsed() > config_.max_storage_mb * 1024 * 1024 &&
|
||||
!auto_backups.empty()) {
|
||||
snapshots_.erase(auto_backups.front().second);
|
||||
auto_backups.erase(auto_backups.begin());
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
RomVersionManager::Stats RomVersionManager::GetStats() const {
|
||||
Stats stats = {};
|
||||
stats.total_snapshots = snapshots_.size();
|
||||
|
||||
|
||||
for (const auto& [id, snapshot] : snapshots_) {
|
||||
if (snapshot.is_safe_point) stats.safe_points++;
|
||||
if (snapshot.is_checkpoint) stats.manual_checkpoints++;
|
||||
if (!snapshot.is_checkpoint) stats.auto_backups++;
|
||||
if (snapshot.is_safe_point)
|
||||
stats.safe_points++;
|
||||
if (snapshot.is_checkpoint)
|
||||
stats.manual_checkpoints++;
|
||||
if (!snapshot.is_checkpoint)
|
||||
stats.auto_backups++;
|
||||
stats.total_storage_bytes += snapshot.compressed_size;
|
||||
|
||||
|
||||
if (stats.oldest_snapshot_timestamp == 0 ||
|
||||
snapshot.timestamp < stats.oldest_snapshot_timestamp) {
|
||||
stats.oldest_snapshot_timestamp = snapshot.timestamp;
|
||||
}
|
||||
|
||||
|
||||
if (snapshot.timestamp > stats.newest_snapshot_timestamp) {
|
||||
stats.newest_snapshot_timestamp = snapshot.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -329,7 +328,7 @@ std::string RomVersionManager::ComputeRomHash() const {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
std::vector<uint8_t> data(rom_->size());
|
||||
std::memcpy(data.data(), rom_->data(), rom_->size());
|
||||
return ComputeHash(data);
|
||||
@@ -352,18 +351,18 @@ absl::Status RomVersionManager::ValidateRomIntegrity() const {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Basic checks
|
||||
if (rom_->size() == 0) {
|
||||
return absl::DataLossError("ROM size is zero");
|
||||
}
|
||||
|
||||
|
||||
// Check for valid SNES header
|
||||
// (This is a simplified check - real validation would be more thorough)
|
||||
if (rom_->size() < 0x8000) {
|
||||
return absl::DataLossError("ROM too small to be valid");
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -380,9 +379,7 @@ size_t RomVersionManager::GetTotalStorageUsed() const {
|
||||
// ============================================================================
|
||||
|
||||
ProposalApprovalManager::ProposalApprovalManager(RomVersionManager* version_mgr)
|
||||
: version_mgr_(version_mgr),
|
||||
mode_(ApprovalMode::kHostOnly) {
|
||||
}
|
||||
: version_mgr_(version_mgr), mode_(ApprovalMode::kHostOnly) {}
|
||||
|
||||
void ProposalApprovalManager::SetApprovalMode(ApprovalMode mode) {
|
||||
mode_ = mode;
|
||||
@@ -393,53 +390,46 @@ void ProposalApprovalManager::SetHost(const std::string& host_username) {
|
||||
}
|
||||
|
||||
absl::Status ProposalApprovalManager::SubmitProposal(
|
||||
const std::string& proposal_id,
|
||||
const std::string& sender,
|
||||
const std::string& description,
|
||||
const nlohmann::json& proposal_data) {
|
||||
|
||||
const std::string& proposal_id, const std::string& sender,
|
||||
const std::string& description, const nlohmann::json& proposal_data) {
|
||||
ApprovalStatus status;
|
||||
status.proposal_id = proposal_id;
|
||||
status.status = "pending";
|
||||
status.created_at = GetCurrentTimestamp();
|
||||
status.decided_at = 0;
|
||||
|
||||
|
||||
// Create snapshot before potential application
|
||||
auto snapshot_result = version_mgr_->CreateSnapshot(
|
||||
absl::StrCat("Before proposal: ", description),
|
||||
sender,
|
||||
false);
|
||||
|
||||
absl::StrCat("Before proposal: ", description), sender, false);
|
||||
|
||||
if (!snapshot_result.ok()) {
|
||||
return snapshot_result.status();
|
||||
}
|
||||
|
||||
|
||||
status.snapshot_before = *snapshot_result;
|
||||
|
||||
|
||||
proposals_[proposal_id] = status;
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ProposalApprovalManager::VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
const std::string& username,
|
||||
const std::string& proposal_id, const std::string& username,
|
||||
bool approved) {
|
||||
|
||||
auto it = proposals_.find(proposal_id);
|
||||
if (it == proposals_.end()) {
|
||||
return absl::NotFoundError("Proposal not found");
|
||||
}
|
||||
|
||||
|
||||
ApprovalStatus& status = it->second;
|
||||
|
||||
|
||||
if (status.status != "pending") {
|
||||
return absl::FailedPreconditionError("Proposal already decided");
|
||||
}
|
||||
|
||||
|
||||
// Record vote
|
||||
status.votes[username] = approved;
|
||||
|
||||
|
||||
// Check if decision can be made
|
||||
if (CheckApprovalThreshold(status)) {
|
||||
status.status = "approved";
|
||||
@@ -448,23 +438,23 @@ absl::Status ProposalApprovalManager::VoteOnProposal(
|
||||
// Check if rejection threshold reached
|
||||
size_t rejection_count = 0;
|
||||
for (const auto& [user, vote] : status.votes) {
|
||||
if (!vote) rejection_count++;
|
||||
if (!vote)
|
||||
rejection_count++;
|
||||
}
|
||||
|
||||
|
||||
// If host rejected (in host-only mode), reject immediately
|
||||
if (mode_ == ApprovalMode::kHostOnly &&
|
||||
username == host_username_ && !approved) {
|
||||
if (mode_ == ApprovalMode::kHostOnly && username == host_username_ &&
|
||||
!approved) {
|
||||
status.status = "rejected";
|
||||
status.decided_at = GetCurrentTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool ProposalApprovalManager::CheckApprovalThreshold(
|
||||
const ApprovalStatus& status) const {
|
||||
|
||||
switch (mode_) {
|
||||
case ApprovalMode::kHostOnly:
|
||||
// Only host vote matters
|
||||
@@ -472,29 +462,31 @@ bool ProposalApprovalManager::CheckApprovalThreshold(
|
||||
return status.votes.at(host_username_);
|
||||
}
|
||||
return false;
|
||||
|
||||
|
||||
case ApprovalMode::kMajorityVote: {
|
||||
size_t approval_count = 0;
|
||||
for (const auto& [user, approved] : status.votes) {
|
||||
if (approved) approval_count++;
|
||||
if (approved)
|
||||
approval_count++;
|
||||
}
|
||||
return approval_count > participants_.size() / 2;
|
||||
}
|
||||
|
||||
|
||||
case ApprovalMode::kUnanimous: {
|
||||
if (status.votes.size() < participants_.size()) {
|
||||
return false; // Not everyone voted yet
|
||||
}
|
||||
for (const auto& [user, approved] : status.votes) {
|
||||
if (!approved) return false;
|
||||
if (!approved)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
case ApprovalMode::kAutoApprove:
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -507,7 +499,7 @@ bool ProposalApprovalManager::IsProposalApproved(
|
||||
return it->second.status == "approved";
|
||||
}
|
||||
|
||||
std::vector<ProposalApprovalManager::ApprovalStatus>
|
||||
std::vector<ProposalApprovalManager::ApprovalStatus>
|
||||
ProposalApprovalManager::GetPendingProposals() const {
|
||||
std::vector<ApprovalStatus> pending;
|
||||
for (const auto& [id, status] : proposals_) {
|
||||
@@ -518,7 +510,7 @@ ProposalApprovalManager::GetPendingProposals() const {
|
||||
return pending;
|
||||
}
|
||||
|
||||
absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
|
||||
absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
|
||||
ProposalApprovalManager::GetProposalStatus(
|
||||
const std::string& proposal_id) const {
|
||||
auto it = proposals_.find(proposal_id);
|
||||
|
||||
@@ -29,12 +29,12 @@ struct RomSnapshot {
|
||||
std::string rom_hash;
|
||||
std::vector<uint8_t> rom_data;
|
||||
size_t compressed_size;
|
||||
|
||||
|
||||
// Metadata
|
||||
std::string creator;
|
||||
bool is_checkpoint; // Manual checkpoint vs auto-backup
|
||||
bool is_safe_point; // Marked as "known good" by host
|
||||
|
||||
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json metadata; // Custom metadata (proposals applied, etc.)
|
||||
#endif
|
||||
@@ -55,7 +55,7 @@ struct VersionDiff {
|
||||
/**
|
||||
* @class RomVersionManager
|
||||
* @brief Manages ROM versioning, snapshots, and rollback capabilities
|
||||
*
|
||||
*
|
||||
* Provides:
|
||||
* - Automatic periodic snapshots
|
||||
* - Manual checkpoints
|
||||
@@ -73,87 +73,84 @@ class RomVersionManager {
|
||||
bool compress_snapshots = true;
|
||||
bool enable_corruption_detection = true;
|
||||
};
|
||||
|
||||
|
||||
explicit RomVersionManager(Rom* rom);
|
||||
~RomVersionManager();
|
||||
|
||||
|
||||
/**
|
||||
* Initialize version management
|
||||
*/
|
||||
absl::Status Initialize(const Config& config);
|
||||
|
||||
|
||||
/**
|
||||
* Create a snapshot of current ROM state
|
||||
*/
|
||||
absl::StatusOr<std::string> CreateSnapshot(
|
||||
const std::string& description,
|
||||
const std::string& creator,
|
||||
bool is_checkpoint = false);
|
||||
|
||||
absl::StatusOr<std::string> CreateSnapshot(const std::string& description,
|
||||
const std::string& creator,
|
||||
bool is_checkpoint = false);
|
||||
|
||||
/**
|
||||
* Restore ROM to a previous snapshot
|
||||
*/
|
||||
absl::Status RestoreSnapshot(const std::string& snapshot_id);
|
||||
|
||||
|
||||
/**
|
||||
* Mark a snapshot as a safe point (host-verified)
|
||||
*/
|
||||
absl::Status MarkAsSafePoint(const std::string& snapshot_id);
|
||||
|
||||
|
||||
/**
|
||||
* Get all snapshots, sorted by timestamp
|
||||
*/
|
||||
std::vector<RomSnapshot> GetSnapshots(bool safe_points_only = false) const;
|
||||
|
||||
|
||||
/**
|
||||
* Get a specific snapshot
|
||||
*/
|
||||
absl::StatusOr<RomSnapshot> GetSnapshot(const std::string& snapshot_id) const;
|
||||
|
||||
|
||||
/**
|
||||
* Delete a snapshot
|
||||
*/
|
||||
absl::Status DeleteSnapshot(const std::string& snapshot_id);
|
||||
|
||||
|
||||
/**
|
||||
* Generate diff between two snapshots
|
||||
*/
|
||||
absl::StatusOr<VersionDiff> GenerateDiff(
|
||||
const std::string& from_id,
|
||||
const std::string& to_id) const;
|
||||
|
||||
absl::StatusOr<VersionDiff> GenerateDiff(const std::string& from_id,
|
||||
const std::string& to_id) const;
|
||||
|
||||
/**
|
||||
* Check for ROM corruption
|
||||
*/
|
||||
absl::StatusOr<bool> DetectCorruption();
|
||||
|
||||
|
||||
/**
|
||||
* Auto-recover from corruption using nearest safe point
|
||||
*/
|
||||
absl::Status AutoRecover();
|
||||
|
||||
|
||||
/**
|
||||
* Export snapshot to file
|
||||
*/
|
||||
absl::Status ExportSnapshot(
|
||||
const std::string& snapshot_id,
|
||||
const std::string& filepath);
|
||||
|
||||
absl::Status ExportSnapshot(const std::string& snapshot_id,
|
||||
const std::string& filepath);
|
||||
|
||||
/**
|
||||
* Import snapshot from file
|
||||
*/
|
||||
absl::Status ImportSnapshot(const std::string& filepath);
|
||||
|
||||
|
||||
/**
|
||||
* Get current ROM hash
|
||||
*/
|
||||
std::string GetCurrentHash() const;
|
||||
|
||||
|
||||
/**
|
||||
* Cleanup old snapshots based on policy
|
||||
*/
|
||||
absl::Status CleanupOldSnapshots();
|
||||
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
@@ -167,18 +164,19 @@ class RomVersionManager {
|
||||
int64_t newest_snapshot_timestamp;
|
||||
};
|
||||
Stats GetStats() const;
|
||||
|
||||
|
||||
private:
|
||||
Rom* rom_;
|
||||
Config config_;
|
||||
std::map<std::string, RomSnapshot> snapshots_;
|
||||
std::string last_known_hash_;
|
||||
int64_t last_backup_time_;
|
||||
|
||||
|
||||
// Helper functions
|
||||
std::string ComputeRomHash() const;
|
||||
std::vector<uint8_t> CompressData(const std::vector<uint8_t>& data) const;
|
||||
std::vector<uint8_t> DecompressData(const std::vector<uint8_t>& compressed) const;
|
||||
std::vector<uint8_t> DecompressData(
|
||||
const std::vector<uint8_t>& compressed) const;
|
||||
absl::Status ValidateRomIntegrity() const;
|
||||
size_t GetTotalStorageUsed() const;
|
||||
void PruneOldSnapshots();
|
||||
@@ -187,7 +185,7 @@ class RomVersionManager {
|
||||
/**
|
||||
* @class ProposalApprovalManager
|
||||
* @brief Manages proposal approval workflow for collaborative sessions
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Host approval required for all changes
|
||||
* - Participant voting system
|
||||
@@ -197,12 +195,12 @@ class RomVersionManager {
|
||||
class ProposalApprovalManager {
|
||||
public:
|
||||
enum class ApprovalMode {
|
||||
kHostOnly, // Only host can approve
|
||||
kMajorityVote, // Majority of participants must approve
|
||||
kUnanimous, // All participants must approve
|
||||
kAutoApprove // No approval needed (dangerous!)
|
||||
kHostOnly, // Only host can approve
|
||||
kMajorityVote, // Majority of participants must approve
|
||||
kUnanimous, // All participants must approve
|
||||
kAutoApprove // No approval needed (dangerous!)
|
||||
};
|
||||
|
||||
|
||||
struct ApprovalStatus {
|
||||
std::string proposal_id;
|
||||
std::string status; // "pending", "approved", "rejected", "applied"
|
||||
@@ -212,76 +210,71 @@ class ProposalApprovalManager {
|
||||
std::string snapshot_before; // Snapshot ID before applying
|
||||
std::string snapshot_after; // Snapshot ID after applying
|
||||
};
|
||||
|
||||
|
||||
explicit ProposalApprovalManager(RomVersionManager* version_mgr);
|
||||
|
||||
|
||||
/**
|
||||
* Set approval mode for the session
|
||||
*/
|
||||
void SetApprovalMode(ApprovalMode mode);
|
||||
|
||||
|
||||
/**
|
||||
* Set host username
|
||||
*/
|
||||
void SetHost(const std::string& host_username);
|
||||
|
||||
|
||||
/**
|
||||
* Submit a proposal for approval
|
||||
*/
|
||||
absl::Status SubmitProposal(
|
||||
const std::string& proposal_id,
|
||||
const std::string& sender,
|
||||
const std::string& description,
|
||||
const nlohmann::json& proposal_data);
|
||||
|
||||
absl::Status SubmitProposal(const std::string& proposal_id,
|
||||
const std::string& sender,
|
||||
const std::string& description,
|
||||
const nlohmann::json& proposal_data);
|
||||
|
||||
/**
|
||||
* Vote on a proposal
|
||||
*/
|
||||
absl::Status VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
const std::string& username,
|
||||
bool approved);
|
||||
|
||||
absl::Status VoteOnProposal(const std::string& proposal_id,
|
||||
const std::string& username, bool approved);
|
||||
|
||||
/**
|
||||
* Apply an approved proposal
|
||||
*/
|
||||
absl::Status ApplyProposal(
|
||||
const std::string& proposal_id,
|
||||
Rom* rom);
|
||||
|
||||
absl::Status ApplyProposal(const std::string& proposal_id, Rom* rom);
|
||||
|
||||
/**
|
||||
* Reject and rollback a proposal
|
||||
*/
|
||||
absl::Status RejectProposal(const std::string& proposal_id);
|
||||
|
||||
|
||||
/**
|
||||
* Get proposal status
|
||||
*/
|
||||
absl::StatusOr<ApprovalStatus> GetProposalStatus(
|
||||
const std::string& proposal_id) const;
|
||||
|
||||
|
||||
/**
|
||||
* Get all pending proposals
|
||||
*/
|
||||
std::vector<ApprovalStatus> GetPendingProposals() const;
|
||||
|
||||
|
||||
/**
|
||||
* Check if proposal is approved
|
||||
*/
|
||||
bool IsProposalApproved(const std::string& proposal_id) const;
|
||||
|
||||
|
||||
/**
|
||||
* Get audit log
|
||||
*/
|
||||
std::vector<ApprovalStatus> GetAuditLog() const;
|
||||
|
||||
|
||||
private:
|
||||
RomVersionManager* version_mgr_;
|
||||
ApprovalMode mode_;
|
||||
std::string host_username_;
|
||||
std::map<std::string, ApprovalStatus> proposals_;
|
||||
std::vector<std::string> participants_;
|
||||
|
||||
|
||||
bool CheckApprovalThreshold(const ApprovalStatus& status) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
// Cross-platform WebSocket support using httplib
|
||||
#ifdef YAZE_WITH_JSON
|
||||
#ifndef _WIN32
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#endif
|
||||
#include "httplib.h"
|
||||
#endif
|
||||
|
||||
@@ -23,112 +25,111 @@ namespace net {
|
||||
class WebSocketClient::Impl {
|
||||
public:
|
||||
Impl() : connected_(false), should_stop_(false) {}
|
||||
|
||||
~Impl() {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
|
||||
~Impl() { Disconnect(); }
|
||||
|
||||
absl::Status Connect(const std::string& host, int port) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
|
||||
if (connected_) {
|
||||
return absl::AlreadyExistsError("Already connected");
|
||||
}
|
||||
|
||||
|
||||
host_ = host;
|
||||
port_ = port;
|
||||
|
||||
|
||||
try {
|
||||
// httplib WebSocket connection (cross-platform)
|
||||
std::string url = absl::StrFormat("ws://%s:%d", host, port);
|
||||
|
||||
|
||||
// Create WebSocket connection
|
||||
client_ = std::make_unique<httplib::Client>(host, port);
|
||||
client_->set_connection_timeout(5, 0); // 5 seconds
|
||||
client_->set_read_timeout(30, 0); // 30 seconds
|
||||
|
||||
client_->set_read_timeout(30, 0); // 30 seconds
|
||||
|
||||
connected_ = true;
|
||||
should_stop_ = false;
|
||||
|
||||
|
||||
// Start receive thread
|
||||
receive_thread_ = std::thread([this]() { ReceiveLoop(); });
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
return absl::UnavailableError(
|
||||
absl::StrCat("Failed to connect: ", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Disconnect() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!connected_) return;
|
||||
|
||||
|
||||
if (!connected_)
|
||||
return;
|
||||
|
||||
should_stop_ = true;
|
||||
connected_ = false;
|
||||
|
||||
|
||||
if (receive_thread_.joinable()) {
|
||||
receive_thread_.join();
|
||||
}
|
||||
|
||||
|
||||
client_.reset();
|
||||
}
|
||||
|
||||
|
||||
absl::Status Send(const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
|
||||
if (!connected_) {
|
||||
return absl::FailedPreconditionError("Not connected");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// In a real implementation, this would use WebSocket send
|
||||
// For now, we'll use HTTP POST as fallback
|
||||
auto res = client_->Post("/message", message, "application/json");
|
||||
|
||||
|
||||
if (!res) {
|
||||
return absl::UnavailableError("Failed to send message");
|
||||
}
|
||||
|
||||
|
||||
if (res->status != 200) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("Server error: %d", res->status));
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InternalError(absl::StrCat("Send failed: ", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SetMessageCallback(std::function<void(const std::string&)> callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
message_callback_ = callback;
|
||||
}
|
||||
|
||||
|
||||
void SetErrorCallback(std::function<void(const std::string&)> callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
error_callback_ = callback;
|
||||
}
|
||||
|
||||
|
||||
bool IsConnected() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return connected_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void ReceiveLoop() {
|
||||
while (!should_stop_) {
|
||||
try {
|
||||
// Poll for messages (platform-independent)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
|
||||
// In a real WebSocket implementation, this would receive messages
|
||||
// For now, this is a placeholder for the receive loop
|
||||
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
if (error_callback_) {
|
||||
error_callback_(e.what());
|
||||
@@ -136,16 +137,16 @@ class WebSocketClient::Impl {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::unique_ptr<httplib::Client> client_;
|
||||
std::thread receive_thread_;
|
||||
|
||||
|
||||
std::string host_;
|
||||
int port_;
|
||||
bool connected_;
|
||||
bool should_stop_;
|
||||
|
||||
|
||||
std::function<void(const std::string&)> message_callback_;
|
||||
std::function<void(const std::string&)> error_callback_;
|
||||
};
|
||||
@@ -174,9 +175,7 @@ class WebSocketClient::Impl {
|
||||
// ============================================================================
|
||||
|
||||
WebSocketClient::WebSocketClient()
|
||||
: impl_(std::make_unique<Impl>()),
|
||||
state_(ConnectionState::kDisconnected) {
|
||||
}
|
||||
: impl_(std::make_unique<Impl>()), state_(ConnectionState::kDisconnected) {}
|
||||
|
||||
WebSocketClient::~WebSocketClient() {
|
||||
Disconnect();
|
||||
@@ -184,13 +183,13 @@ WebSocketClient::~WebSocketClient() {
|
||||
|
||||
absl::Status WebSocketClient::Connect(const std::string& host, int port) {
|
||||
auto status = impl_->Connect(host, port);
|
||||
|
||||
|
||||
if (status.ok()) {
|
||||
SetState(ConnectionState::kConnected);
|
||||
} else {
|
||||
SetState(ConnectionState::kError);
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -201,31 +200,25 @@ void WebSocketClient::Disconnect() {
|
||||
}
|
||||
|
||||
absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
|
||||
const std::string& session_name,
|
||||
const std::string& username,
|
||||
const std::string& rom_hash,
|
||||
bool ai_enabled) {
|
||||
|
||||
const std::string& session_name, const std::string& username,
|
||||
const std::string& rom_hash, bool ai_enabled) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
if (!IsConnected()) {
|
||||
return absl::FailedPreconditionError("Not connected to server");
|
||||
}
|
||||
|
||||
nlohmann::json message = {
|
||||
{"type", "host_session"},
|
||||
{"payload", {
|
||||
{"session_name", session_name},
|
||||
{"username", username},
|
||||
{"rom_hash", rom_hash},
|
||||
{"ai_enabled", ai_enabled}
|
||||
}}
|
||||
};
|
||||
|
||||
|
||||
nlohmann::json message = {{"type", "host_session"},
|
||||
{"payload",
|
||||
{{"session_name", session_name},
|
||||
{"username", username},
|
||||
{"rom_hash", rom_hash},
|
||||
{"ai_enabled", ai_enabled}}}};
|
||||
|
||||
auto status = SendRaw(message);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// In a real implementation, we'd wait for the server response
|
||||
// For now, return a placeholder
|
||||
SessionInfo session;
|
||||
@@ -233,7 +226,7 @@ absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
|
||||
session.host = username;
|
||||
session.rom_hash = rom_hash;
|
||||
session.ai_enabled = ai_enabled;
|
||||
|
||||
|
||||
current_session_ = session;
|
||||
return session;
|
||||
#else
|
||||
@@ -242,31 +235,25 @@ absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
|
||||
}
|
||||
|
||||
absl::StatusOr<SessionInfo> WebSocketClient::JoinSession(
|
||||
const std::string& session_code,
|
||||
const std::string& username) {
|
||||
|
||||
const std::string& session_code, const std::string& username) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
if (!IsConnected()) {
|
||||
return absl::FailedPreconditionError("Not connected to server");
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json message = {
|
||||
{"type", "join_session"},
|
||||
{"payload", {
|
||||
{"session_code", session_code},
|
||||
{"username", username}
|
||||
}}
|
||||
};
|
||||
|
||||
{"type", "join_session"},
|
||||
{"payload", {{"session_code", session_code}, {"username", username}}}};
|
||||
|
||||
auto status = SendRaw(message);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// Placeholder - would wait for server response
|
||||
SessionInfo session;
|
||||
session.session_code = session_code;
|
||||
|
||||
|
||||
current_session_ = session;
|
||||
return session;
|
||||
#else
|
||||
@@ -279,12 +266,9 @@ absl::Status WebSocketClient::LeaveSession() {
|
||||
if (!InSession()) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
nlohmann::json message = {
|
||||
{"type", "leave_session"},
|
||||
{"payload", {}}
|
||||
};
|
||||
|
||||
|
||||
nlohmann::json message = {{"type", "leave_session"}, {"payload", {}}};
|
||||
|
||||
auto status = SendRaw(message);
|
||||
current_session_ = SessionInfo{};
|
||||
return status;
|
||||
@@ -293,80 +277,57 @@ absl::Status WebSocketClient::LeaveSession() {
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status WebSocketClient::SendChatMessage(
|
||||
const std::string& message,
|
||||
const std::string& sender) {
|
||||
|
||||
absl::Status WebSocketClient::SendChatMessage(const std::string& message,
|
||||
const std::string& sender) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json msg = {
|
||||
{"type", "chat_message"},
|
||||
{"payload", {
|
||||
{"message", message},
|
||||
{"sender", sender}
|
||||
}}
|
||||
};
|
||||
|
||||
{"type", "chat_message"},
|
||||
{"payload", {{"message", message}, {"sender", sender}}}};
|
||||
|
||||
return SendRaw(msg);
|
||||
#else
|
||||
return absl::UnimplementedError("JSON support required");
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status WebSocketClient::SendRomSync(
|
||||
const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender) {
|
||||
|
||||
absl::Status WebSocketClient::SendRomSync(const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json message = {
|
||||
{"type", "rom_sync"},
|
||||
{"payload", {
|
||||
{"diff_data", diff_data},
|
||||
{"rom_hash", rom_hash},
|
||||
{"sender", sender}
|
||||
}}
|
||||
};
|
||||
|
||||
{"type", "rom_sync"},
|
||||
{"payload",
|
||||
{{"diff_data", diff_data}, {"rom_hash", rom_hash}, {"sender", sender}}}};
|
||||
|
||||
return SendRaw(message);
|
||||
#else
|
||||
return absl::UnimplementedError("JSON support required");
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status WebSocketClient::ShareProposal(
|
||||
const nlohmann::json& proposal_data,
|
||||
const std::string& sender) {
|
||||
|
||||
absl::Status WebSocketClient::ShareProposal(const nlohmann::json& proposal_data,
|
||||
const std::string& sender) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json message = {
|
||||
{"type", "proposal_share"},
|
||||
{"payload", {
|
||||
{"sender", sender},
|
||||
{"proposal_data", proposal_data}
|
||||
}}
|
||||
};
|
||||
|
||||
{"type", "proposal_share"},
|
||||
{"payload", {{"sender", sender}, {"proposal_data", proposal_data}}}};
|
||||
|
||||
return SendRaw(message);
|
||||
#else
|
||||
return absl::UnimplementedError("JSON support required");
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status WebSocketClient::VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
bool approved,
|
||||
const std::string& username) {
|
||||
|
||||
absl::Status WebSocketClient::VoteOnProposal(const std::string& proposal_id,
|
||||
bool approved,
|
||||
const std::string& username) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json message = {
|
||||
{"type", "proposal_vote"},
|
||||
{"payload", {
|
||||
{"proposal_id", proposal_id},
|
||||
{"approved", approved},
|
||||
{"username", username}
|
||||
}}
|
||||
};
|
||||
|
||||
nlohmann::json message = {{"type", "proposal_vote"},
|
||||
{"payload",
|
||||
{{"proposal_id", proposal_id},
|
||||
{"approved", approved},
|
||||
{"username", username}}}};
|
||||
|
||||
return SendRaw(message);
|
||||
#else
|
||||
return absl::UnimplementedError("JSON support required");
|
||||
@@ -374,25 +335,20 @@ absl::Status WebSocketClient::VoteOnProposal(
|
||||
}
|
||||
|
||||
absl::Status WebSocketClient::UpdateProposalStatus(
|
||||
const std::string& proposal_id,
|
||||
const std::string& status) {
|
||||
|
||||
const std::string& proposal_id, const std::string& status) {
|
||||
#ifdef YAZE_WITH_JSON
|
||||
nlohmann::json message = {
|
||||
{"type", "proposal_update"},
|
||||
{"payload", {
|
||||
{"proposal_id", proposal_id},
|
||||
{"status", status}
|
||||
}}
|
||||
};
|
||||
|
||||
{"type", "proposal_update"},
|
||||
{"payload", {{"proposal_id", proposal_id}, {"status", status}}}};
|
||||
|
||||
return SendRaw(message);
|
||||
#else
|
||||
return absl::UnimplementedError("JSON support required");
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebSocketClient::OnMessage(const std::string& type, MessageCallback callback) {
|
||||
void WebSocketClient::OnMessage(const std::string& type,
|
||||
MessageCallback callback) {
|
||||
message_callbacks_[type].push_back(callback);
|
||||
}
|
||||
|
||||
@@ -418,7 +374,7 @@ void WebSocketClient::HandleMessage(const std::string& message) {
|
||||
try {
|
||||
auto json = nlohmann::json::parse(message);
|
||||
std::string type = json["type"];
|
||||
|
||||
|
||||
auto it = message_callbacks_.find(type);
|
||||
if (it != message_callbacks_.end()) {
|
||||
for (auto& callback : it->second) {
|
||||
|
||||
@@ -48,7 +48,7 @@ struct SessionInfo {
|
||||
/**
|
||||
* @class WebSocketClient
|
||||
* @brief WebSocket client for connecting to yaze-server
|
||||
*
|
||||
*
|
||||
* Provides:
|
||||
* - Connection management with auto-reconnect
|
||||
* - Session hosting and joining
|
||||
@@ -61,148 +61,138 @@ class WebSocketClient {
|
||||
using MessageCallback = std::function<void(const nlohmann::json&)>;
|
||||
using ErrorCallback = std::function<void(const std::string&)>;
|
||||
using StateCallback = std::function<void(ConnectionState)>;
|
||||
|
||||
|
||||
WebSocketClient();
|
||||
~WebSocketClient();
|
||||
|
||||
|
||||
/**
|
||||
* Connect to yaze-server
|
||||
* @param host Server hostname/IP
|
||||
* @param port Server port (default: 8765)
|
||||
*/
|
||||
absl::Status Connect(const std::string& host, int port = 8765);
|
||||
|
||||
|
||||
/**
|
||||
* Disconnect from server
|
||||
*/
|
||||
void Disconnect();
|
||||
|
||||
|
||||
/**
|
||||
* Host a new collaboration session
|
||||
*/
|
||||
absl::StatusOr<SessionInfo> HostSession(
|
||||
const std::string& session_name,
|
||||
const std::string& username,
|
||||
const std::string& rom_hash,
|
||||
bool ai_enabled = true);
|
||||
|
||||
absl::StatusOr<SessionInfo> HostSession(const std::string& session_name,
|
||||
const std::string& username,
|
||||
const std::string& rom_hash,
|
||||
bool ai_enabled = true);
|
||||
|
||||
/**
|
||||
* Join an existing session
|
||||
*/
|
||||
absl::StatusOr<SessionInfo> JoinSession(
|
||||
const std::string& session_code,
|
||||
const std::string& username);
|
||||
|
||||
absl::StatusOr<SessionInfo> JoinSession(const std::string& session_code,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Leave current session
|
||||
*/
|
||||
absl::Status LeaveSession();
|
||||
|
||||
|
||||
/**
|
||||
* Send chat message
|
||||
*/
|
||||
absl::Status SendChatMessage(
|
||||
const std::string& message,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status SendChatMessage(const std::string& message,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Send ROM sync
|
||||
*/
|
||||
absl::Status SendRomSync(
|
||||
const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status SendRomSync(const std::string& diff_data,
|
||||
const std::string& rom_hash,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Share snapshot
|
||||
*/
|
||||
absl::Status ShareSnapshot(
|
||||
const std::string& snapshot_data,
|
||||
const std::string& snapshot_type,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status ShareSnapshot(const std::string& snapshot_data,
|
||||
const std::string& snapshot_type,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Share proposal for approval
|
||||
*/
|
||||
absl::Status ShareProposal(
|
||||
const nlohmann::json& proposal_data,
|
||||
const std::string& sender);
|
||||
|
||||
absl::Status ShareProposal(const nlohmann::json& proposal_data,
|
||||
const std::string& sender);
|
||||
|
||||
/**
|
||||
* Vote on proposal (approve/reject)
|
||||
*/
|
||||
absl::Status VoteOnProposal(
|
||||
const std::string& proposal_id,
|
||||
bool approved,
|
||||
const std::string& username);
|
||||
|
||||
absl::Status VoteOnProposal(const std::string& proposal_id, bool approved,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Update proposal status
|
||||
*/
|
||||
absl::Status UpdateProposalStatus(
|
||||
const std::string& proposal_id,
|
||||
const std::string& status);
|
||||
|
||||
absl::Status UpdateProposalStatus(const std::string& proposal_id,
|
||||
const std::string& status);
|
||||
|
||||
/**
|
||||
* Send AI query
|
||||
*/
|
||||
absl::Status SendAIQuery(
|
||||
const std::string& query,
|
||||
const std::string& username);
|
||||
|
||||
absl::Status SendAIQuery(const std::string& query,
|
||||
const std::string& username);
|
||||
|
||||
/**
|
||||
* Register callback for specific message type
|
||||
*/
|
||||
void OnMessage(const std::string& type, MessageCallback callback);
|
||||
|
||||
|
||||
/**
|
||||
* Register callback for errors
|
||||
*/
|
||||
void OnError(ErrorCallback callback);
|
||||
|
||||
|
||||
/**
|
||||
* Register callback for connection state changes
|
||||
*/
|
||||
void OnStateChange(StateCallback callback);
|
||||
|
||||
|
||||
/**
|
||||
* Get current connection state
|
||||
*/
|
||||
ConnectionState GetState() const { return state_; }
|
||||
|
||||
|
||||
/**
|
||||
* Get current session info (if in a session)
|
||||
*/
|
||||
absl::StatusOr<SessionInfo> GetSessionInfo() const;
|
||||
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
bool IsConnected() const { return state_ == ConnectionState::kConnected; }
|
||||
|
||||
|
||||
/**
|
||||
* Check if in a session
|
||||
*/
|
||||
bool InSession() const { return !current_session_.session_id.empty(); }
|
||||
|
||||
|
||||
private:
|
||||
// Implementation details (using native WebSocket or library)
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
|
||||
ConnectionState state_;
|
||||
SessionInfo current_session_;
|
||||
|
||||
|
||||
// Callbacks
|
||||
std::map<std::string, std::vector<MessageCallback>> message_callbacks_;
|
||||
std::vector<ErrorCallback> error_callbacks_;
|
||||
std::vector<StateCallback> state_callbacks_;
|
||||
|
||||
|
||||
// Internal message handling
|
||||
void HandleMessage(const std::string& message);
|
||||
void HandleError(const std::string& error);
|
||||
void SetState(ConnectionState state);
|
||||
|
||||
|
||||
// Send raw message
|
||||
absl::Status SendRaw(const nlohmann::json& message);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user