302 lines
9.9 KiB
C++
302 lines
9.9 KiB
C++
|
/*
|
||
|
* 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_sch.h>
|
||
|
#include <api/api_sch_utils.h>
|
||
|
#include <api/api_utils.h>
|
||
|
#include <magic_enum.hpp>
|
||
|
#include <sch_commit.h>
|
||
|
#include <sch_edit_frame.h>
|
||
|
#include <wx/filename.h>
|
||
|
|
||
|
#include <api/common/types/base_types.pb.h>
|
||
|
|
||
|
using namespace kiapi::common::commands;
|
||
|
using kiapi::common::types::CommandStatus;
|
||
|
using kiapi::common::types::DocumentType;
|
||
|
using kiapi::common::types::ItemRequestStatus;
|
||
|
|
||
|
|
||
|
API_HANDLER_SCH::API_HANDLER_SCH( SCH_EDIT_FRAME* aFrame ) :
|
||
|
API_HANDLER_EDITOR(),
|
||
|
m_frame( aFrame )
|
||
|
{
|
||
|
registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
|
||
|
&API_HANDLER_SCH::handleGetOpenDocuments );
|
||
|
}
|
||
|
|
||
|
|
||
|
std::unique_ptr<COMMIT> API_HANDLER_SCH::createCommit()
|
||
|
{
|
||
|
return std::make_unique<SCH_COMMIT>( m_frame );
|
||
|
}
|
||
|
|
||
|
|
||
|
bool API_HANDLER_SCH::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
|
||
|
{
|
||
|
if( aDocument.type() != DocumentType::DOCTYPE_SCHEMATIC )
|
||
|
return false;
|
||
|
|
||
|
// TODO(JE) need serdes for SCH_SHEET_PATH <> SheetPath
|
||
|
return true;
|
||
|
//wxString currentPath = m_frame->GetCurrentSheet().PathAsString();
|
||
|
//return 0 == aDocument.sheet_path().compare( currentPath.ToStdString() );
|
||
|
}
|
||
|
|
||
|
|
||
|
HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_SCH::handleGetOpenDocuments(
|
||
|
GetOpenDocuments& aMsg, const HANDLER_CONTEXT& )
|
||
|
{
|
||
|
if( aMsg.type() != DocumentType::DOCTYPE_SCHEMATIC )
|
||
|
{
|
||
|
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 );
|
||
|
}
|
||
|
|
||
|
GetOpenDocumentsResponse response;
|
||
|
common::types::DocumentSpecifier doc;
|
||
|
|
||
|
wxFileName fn( m_frame->GetCurrentFileName() );
|
||
|
|
||
|
doc.set_type( DocumentType::DOCTYPE_SCHEMATIC );
|
||
|
doc.set_board_filename( fn.GetFullName() );
|
||
|
|
||
|
response.mutable_documents()->Add( std::move( doc ) );
|
||
|
return response;
|
||
|
}
|
||
|
|
||
|
|
||
|
HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> API_HANDLER_SCH::createItemForType( KICAD_T aType,
|
||
|
EDA_ITEM* aContainer )
|
||
|
{
|
||
|
if( !aContainer )
|
||
|
{
|
||
|
ApiResponseStatus e;
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( "Tried to create an item in a null container" );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
|
||
|
if( aType == SCH_PIN_T && !dynamic_cast<SCH_SYMBOL*>( aContainer ) )
|
||
|
{
|
||
|
ApiResponseStatus e;
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format( "Tried to create a pin in {}, which is not a symbol",
|
||
|
aContainer->GetFriendlyName().ToStdString() ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
else if( aType == SCH_SYMBOL_T && !dynamic_cast<SCHEMATIC*>( aContainer ) )
|
||
|
{
|
||
|
ApiResponseStatus e;
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format( "Tried to create a symbol in {}, which is not a schematic",
|
||
|
aContainer->GetFriendlyName().ToStdString() ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<EDA_ITEM> created = CreateItemForType( aType, aContainer );
|
||
|
|
||
|
if( !created )
|
||
|
{
|
||
|
ApiResponseStatus e;
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
|
||
|
magic_enum::enum_name( aType ) ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
|
||
|
return created;
|
||
|
}
|
||
|
|
||
|
|
||
|
HANDLER_RESULT<ItemRequestStatus> API_HANDLER_SCH::handleCreateUpdateItemsInternal( bool aCreate,
|
||
|
const HANDLER_CONTEXT& aCtx,
|
||
|
const types::ItemHeader &aHeader,
|
||
|
const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
|
||
|
std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
|
||
|
{
|
||
|
ApiResponseStatus e;
|
||
|
|
||
|
auto containerResult = validateItemHeaderDocument( aHeader );
|
||
|
|
||
|
if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
|
||
|
{
|
||
|
// 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 );
|
||
|
}
|
||
|
else if( !containerResult )
|
||
|
{
|
||
|
e.CopyFrom( containerResult.error() );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
|
||
|
SCH_SCREEN* screen = m_frame->GetScreen();
|
||
|
EE_RTREE& screenItems = screen->Items();
|
||
|
|
||
|
std::map<KIID, EDA_ITEM*> itemUuidMap;
|
||
|
|
||
|
std::for_each( screenItems.begin(), screenItems.end(),
|
||
|
[&]( EDA_ITEM* aItem )
|
||
|
{
|
||
|
itemUuidMap[aItem->m_Uuid] = aItem;
|
||
|
} );
|
||
|
|
||
|
EDA_ITEM* container = nullptr;
|
||
|
|
||
|
if( containerResult->has_value() )
|
||
|
{
|
||
|
const KIID& containerId = **containerResult;
|
||
|
|
||
|
if( itemUuidMap.count( containerId ) )
|
||
|
{
|
||
|
container = itemUuidMap.at( containerId );
|
||
|
|
||
|
if( !container )
|
||
|
{
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format(
|
||
|
"The requested container {} is not a valid schematic item container",
|
||
|
containerId.AsStdString() ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format(
|
||
|
"The requested container {} does not exist in this document",
|
||
|
containerId.AsStdString() ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
COMMIT* commit = getCurrentCommit( aCtx );
|
||
|
|
||
|
for( const google::protobuf::Any& anyItem : aItems )
|
||
|
{
|
||
|
ItemStatus status;
|
||
|
std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
|
||
|
|
||
|
if( !type )
|
||
|
{
|
||
|
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
|
||
|
status.set_error_message( fmt::format( "Could not decode a valid type from {}",
|
||
|
anyItem.type_url() ) );
|
||
|
aItemHandler( status, anyItem );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> creationResult =
|
||
|
createItemForType( *type, container );
|
||
|
|
||
|
if( !creationResult )
|
||
|
{
|
||
|
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
|
||
|
status.set_error_message( creationResult.error().error_message() );
|
||
|
aItemHandler( status, anyItem );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<EDA_ITEM> item( std::move( *creationResult ) );
|
||
|
|
||
|
if( !item->Deserialize( anyItem ) )
|
||
|
{
|
||
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
||
|
e.set_error_message( fmt::format( "could not unpack {} from request",
|
||
|
item->GetClass().ToStdString() ) );
|
||
|
return tl::unexpected( e );
|
||
|
}
|
||
|
|
||
|
if( aCreate && itemUuidMap.count( item->m_Uuid ) )
|
||
|
{
|
||
|
status.set_code( ItemStatusCode::ISC_EXISTING );
|
||
|
status.set_error_message( fmt::format( "an item with UUID {} already exists",
|
||
|
item->m_Uuid.AsStdString() ) );
|
||
|
aItemHandler( status, anyItem );
|
||
|
continue;
|
||
|
}
|
||
|
else if( !aCreate && !itemUuidMap.count( item->m_Uuid ) )
|
||
|
{
|
||
|
status.set_code( ItemStatusCode::ISC_NONEXISTENT );
|
||
|
status.set_error_message( fmt::format( "an item with UUID {} does not exist",
|
||
|
item->m_Uuid.AsStdString() ) );
|
||
|
aItemHandler( status, anyItem );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
status.set_code( ItemStatusCode::ISC_OK );
|
||
|
google::protobuf::Any newItem;
|
||
|
|
||
|
if( aCreate )
|
||
|
{
|
||
|
item->Serialize( newItem );
|
||
|
commit->Add( item.release() );
|
||
|
|
||
|
if( !m_activeClients.count( aCtx.ClientName ) )
|
||
|
pushCurrentCommit( aCtx, _( "Added items via API" ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
EDA_ITEM* edaItem = itemUuidMap[item->m_Uuid];
|
||
|
|
||
|
if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( edaItem ) )
|
||
|
{
|
||
|
schItem->SwapData( static_cast<SCH_ITEM*>( item.get() ) );
|
||
|
schItem->Serialize( newItem );
|
||
|
commit->Modify( schItem );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
wxASSERT( false );
|
||
|
}
|
||
|
|
||
|
if( !m_activeClients.count( aCtx.ClientName ) )
|
||
|
pushCurrentCommit( aCtx, _( "Created items via API" ) );
|
||
|
}
|
||
|
|
||
|
aItemHandler( status, newItem );
|
||
|
}
|
||
|
|
||
|
|
||
|
return ItemRequestStatus::IRS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
void API_HANDLER_SCH::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
|
||
|
const HANDLER_CONTEXT& aCtx )
|
||
|
{
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
|
||
|
std::optional<EDA_ITEM*> API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument,
|
||
|
const KIID& aId )
|
||
|
{
|
||
|
if( !validateDocument( aDocument ) )
|
||
|
return std::nullopt;
|
||
|
|
||
|
// TODO
|
||
|
|
||
|
return std::nullopt;
|
||
|
}
|