Files
yaze/src/app/net/collaboration_service.cc
2025-11-21 21:35:50 -05:00

403 lines
11 KiB
C++

#include "app/net/collaboration_service.h"
#include <chrono>
#include <thread>
#include "absl/strings/str_format.h"
namespace yaze {
namespace net {
CollaborationService::CollaborationService(Rom* rom)
: rom_(rom),
version_mgr_(nullptr),
approval_mgr_(nullptr),
client_(std::make_unique<WebSocketClient>()),
sync_in_progress_(false) {}
CollaborationService::~CollaborationService() {
Disconnect();
}
absl::Status CollaborationService::Initialize(
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_updated", [this](const nlohmann::json& payload) {
OnProposalUpdated(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();
}
absl::Status CollaborationService::Connect(const std::string& host, int port) {
return client_->Connect(host, port);
}
void CollaborationService::Disconnect() {
if (client_->IsConnected()) {
client_->Disconnect();
}
}
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
);
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);
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) {
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);
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();
}
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) {
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}};
// 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) {
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);
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 {
// Rollback on error
if (config_.create_snapshot_before_sync) {
auto snapshots = version_mgr_->GetSnapshots();
if (!snapshots.empty()) {
version_mgr_->RestoreSnapshot(snapshots[0].snapshot_id);
}
}
}
sync_in_progress_ = false;
return status;
}
absl::Status CollaborationService::HandleIncomingProposal(
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);
}
absl::Status CollaborationService::VoteOnProposal(
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);
}
return status;
}
bool CollaborationService::IsConnected() const {
return client_->IsConnected();
}
absl::StatusOr<SessionInfo> CollaborationService::GetSessionInfo() const {
return client_->GetSessionInfo();
}
void CollaborationService::SetAutoSync(bool enabled) {
config_.auto_sync_enabled = enabled;
}
// Private callback handlers
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
}
}
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"];
// Update local approval manager state
}
}
void CollaborationService::OnParticipantJoined(const nlohmann::json& payload) {
std::string username = payload["username"];
// Update participant list or notify user
}
void CollaborationService::OnParticipantLeft(const nlohmann::json& payload) {
std::string username = payload["username"];
// Update participant list or notify user
}
// Helper functions
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";
}
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();
}
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;
}
} // namespace net
} // namespace yaze