/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2024 Jon Evans * 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 . */ #include #include #include #include #include #include #include #include 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( &API_HANDLER_SCH::handleGetOpenDocuments ); } std::unique_ptr API_HANDLER_SCH::createCommit() { return std::make_unique( 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 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> 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( 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( 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 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 API_HANDLER_SCH::handleCreateUpdateItemsInternal( bool aCreate, const HANDLER_CONTEXT& aCtx, const types::ItemHeader &aHeader, const google::protobuf::RepeatedPtrField& aItems, std::function 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 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 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> 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 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( edaItem ) ) { schItem->SwapData( static_cast( item.get() ) ); schItem->Serialize( newItem ); commit->Modify( schItem ); } else if( LIB_ITEM* libItem = dynamic_cast( edaItem ) ) { // TODO: there is not currently a way to do this, haha } 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& aItemsToDelete, const HANDLER_CONTEXT& aCtx ) { // TODO } std::optional API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument, const KIID& aId ) { if( !validateDocument( aDocument ) ) return std::nullopt; // TODO return std::nullopt; }