|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2024 Jon Evans <jon@craftyjon.com> * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <api/api_handler_editor.h>
#include <api/api_utils.h>
#include <eda_base_frame.h>
#include <eda_item.h>
#include <wx/wx.h>
using namespace kiapi::common::commands;
API_HANDLER_EDITOR::API_HANDLER_EDITOR( EDA_BASE_FRAME* aFrame ) : API_HANDLER(), m_frame( aFrame ){ registerHandler<BeginCommit, BeginCommitResponse>( &API_HANDLER_EDITOR::handleBeginCommit ); registerHandler<EndCommit, EndCommitResponse>( &API_HANDLER_EDITOR::handleEndCommit ); registerHandler<CreateItems, CreateItemsResponse>( &API_HANDLER_EDITOR::handleCreateItems ); registerHandler<UpdateItems, UpdateItemsResponse>( &API_HANDLER_EDITOR::handleUpdateItems ); registerHandler<DeleteItems, DeleteItemsResponse>( &API_HANDLER_EDITOR::handleDeleteItems ); registerHandler<HitTest, HitTestResponse>( &API_HANDLER_EDITOR::handleHitTest );}
HANDLER_RESULT<BeginCommitResponse> API_HANDLER_EDITOR::handleBeginCommit( BeginCommit& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
if( m_commits.count( aCtx.ClientName ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( fmt::format( "the client {} already has a commit in progress", aCtx.ClientName ) ); return tl::unexpected( e ); }
wxASSERT( !m_activeClients.count( aCtx.ClientName ) );
BeginCommitResponse response;
KIID id; m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() ); response.mutable_id()->set_value( id.AsStdString() );
m_activeClients.insert( aCtx.ClientName );
return response;}
HANDLER_RESULT<EndCommitResponse> API_HANDLER_EDITOR::handleEndCommit( EndCommit& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
if( !m_commits.count( aCtx.ClientName ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( fmt::format( "the client {} does not has a commit in progress", aCtx.ClientName ) ); return tl::unexpected( e ); }
wxASSERT( m_activeClients.count( aCtx.ClientName ) );
const std::pair<KIID, std::unique_ptr<COMMIT>>& pair = m_commits.at( aCtx.ClientName ); const KIID& id = pair.first; const std::unique_ptr<COMMIT>& commit = pair.second;
EndCommitResponse response;
// Do not check IDs with drop; it is a safety net in case the id was lost on the client side
switch( aMsg.action() ) { case kiapi::common::commands::CMA_DROP: { commit->Revert(); m_commits.erase( aCtx.ClientName ); m_activeClients.erase( aCtx.ClientName ); break; }
case kiapi::common::commands::CMA_COMMIT: { if( aMsg.id().value().compare( id.AsStdString() ) != 0 ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( fmt::format( "the id {} does not match the commit in progress", aMsg.id().value() ) ); return tl::unexpected( e ); }
pushCurrentCommit( aCtx, wxString( aMsg.message().c_str(), wxConvUTF8 ) ); break; }
default: break; }
return response;}
COMMIT* API_HANDLER_EDITOR::getCurrentCommit( const HANDLER_CONTEXT& aCtx ){ if( !m_commits.count( aCtx.ClientName ) ) { KIID id; m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() ); }
return m_commits.at( aCtx.ClientName ).second.get();}
void API_HANDLER_EDITOR::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage ){ auto it = m_commits.find( aCtx.ClientName );
if( it == m_commits.end() ) return;
it->second.second->Push( aMessage.IsEmpty() ? m_defaultCommitMessage : aMessage ); m_commits.erase( it ); m_activeClients.erase( aCtx.ClientName );}
HANDLER_RESULT<bool> API_HANDLER_EDITOR::validateDocument( const kiapi::common::types::DocumentSpecifier& aDocument ){ if( !validateDocumentInternal( aDocument ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( fmt::format( "the requested document {} is not open", aDocument.board_filename() ) ); return tl::unexpected( e ); }
return true;}
HANDLER_RESULT<std::optional<KIID>> API_HANDLER_EDITOR::validateItemHeaderDocument( const kiapi::common::types::ItemHeader& aHeader ){ if( !aHeader.has_document() || aHeader.document().type() != thisDocumentType() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_UNHANDLED ); // No error message, this is a flag that the server should try a different handler
return tl::unexpected( e ); }
HANDLER_RESULT<bool> documentValidation = validateDocument( aHeader.document() );
if( !documentValidation ) return tl::unexpected( documentValidation.error() );
if( !validateDocumentInternal( aHeader.document() ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( fmt::format( "the requested document {} is not open", aHeader.document().board_filename() ) ); return tl::unexpected( e ); }
if( aHeader.has_container() ) { return KIID( aHeader.container().value() ); }
// Valid header, but no container provided
return std::nullopt;}
std::optional<ApiResponseStatus> API_HANDLER_EDITOR::checkForBusy(){ if( !m_frame->CanAcceptApiCommands() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BUSY ); e.set_error_message( "KiCad is busy and cannot respond to API requests right now" ); return e; }
return std::nullopt;}
HANDLER_RESULT<CreateItemsResponse> API_HANDLER_EDITOR::handleCreateItems( CreateItems& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
CreateItemsResponse response;
HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( true, aCtx, aMsg.header(), aMsg.items(), [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem ) { ItemCreationResult itemResult; itemResult.mutable_status()->CopyFrom( aStatus ); itemResult.mutable_item()->CopyFrom( aItem ); response.mutable_created_items()->Add( std::move( itemResult ) ); } );
if( !result.has_value() ) return tl::unexpected( result.error() );
response.set_status( *result ); return response;}
HANDLER_RESULT<UpdateItemsResponse> API_HANDLER_EDITOR::handleUpdateItems( UpdateItems& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
UpdateItemsResponse response;
HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( false, aCtx, aMsg.header(), aMsg.items(), [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem ) { ItemUpdateResult itemResult; itemResult.mutable_status()->CopyFrom( aStatus ); itemResult.mutable_item()->CopyFrom( aItem ); response.mutable_updated_items()->Add( std::move( itemResult ) ); } );
if( !result.has_value() ) return tl::unexpected( result.error() );
response.set_status( *result ); return response;}
HANDLER_RESULT<DeleteItemsResponse> API_HANDLER_EDITOR::handleDeleteItems( DeleteItems& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
if( !validateItemHeaderDocument( aMsg.header() ) ) { ApiResponseStatus e; // No message needed for AS_UNHANDLED; this is an internal flag for the API server
e.set_status( ApiStatusCode::AS_UNHANDLED ); return tl::unexpected( e ); }
std::map<KIID, ItemDeletionStatus> itemsToDelete;
for( const kiapi::common::types::KIID& kiidBuf : aMsg.item_ids() ) { if( !kiidBuf.value().empty() ) { KIID kiid( kiidBuf.value() ); itemsToDelete[kiid] = ItemDeletionStatus::IDS_NONEXISTENT; } }
if( itemsToDelete.empty() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "no valid items to delete were given" ); return tl::unexpected( e ); }
deleteItemsInternal( itemsToDelete, aCtx );
DeleteItemsResponse response;
for( const auto& [id, status] : itemsToDelete ) { ItemDeletionResult result; result.mutable_id()->set_value( id.AsStdString() ); result.set_status( status ); }
response.set_status( kiapi::common::types::ItemRequestStatus::IRS_OK ); return response;}
HANDLER_RESULT<HitTestResponse> API_HANDLER_EDITOR::handleHitTest( HitTest& aMsg, const HANDLER_CONTEXT& aCtx ){ if( std::optional<ApiResponseStatus> busy = checkForBusy() ) return tl::unexpected( *busy );
if( !validateItemHeaderDocument( aMsg.header() ) ) { ApiResponseStatus e; // No message needed for AS_UNHANDLED; this is an internal flag for the API server
e.set_status( ApiStatusCode::AS_UNHANDLED ); return tl::unexpected( e ); }
HitTestResponse response;
std::optional<EDA_ITEM*> item = getItemFromDocument( aMsg.header().document(), KIID( aMsg.id().value() ) );
if( !item ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "the requested item ID is not present in the given document" ); return tl::unexpected( e ); }
if( ( *item )->HitTest( kiapi::common::UnpackVector2( aMsg.position() ), aMsg.tolerance() ) ) response.set_result( HitTestResult::HTR_HIT ); else response.set_result( HitTestResult::HTR_NO_HIT );
return response;}
|