You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.9 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2024 Jon Evans <jon@craftyjon.com>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <api/api_handler_sch.h>
  21. #include <api/api_sch_utils.h>
  22. #include <api/api_utils.h>
  23. #include <magic_enum.hpp>
  24. #include <sch_commit.h>
  25. #include <sch_edit_frame.h>
  26. #include <wx/filename.h>
  27. #include <api/common/types/base_types.pb.h>
  28. using namespace kiapi::common::commands;
  29. using kiapi::common::types::CommandStatus;
  30. using kiapi::common::types::DocumentType;
  31. using kiapi::common::types::ItemRequestStatus;
  32. API_HANDLER_SCH::API_HANDLER_SCH( SCH_EDIT_FRAME* aFrame ) :
  33. API_HANDLER_EDITOR(),
  34. m_frame( aFrame )
  35. {
  36. registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
  37. &API_HANDLER_SCH::handleGetOpenDocuments );
  38. }
  39. std::unique_ptr<COMMIT> API_HANDLER_SCH::createCommit()
  40. {
  41. return std::make_unique<SCH_COMMIT>( m_frame );
  42. }
  43. bool API_HANDLER_SCH::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
  44. {
  45. if( aDocument.type() != DocumentType::DOCTYPE_SCHEMATIC )
  46. return false;
  47. // TODO(JE) need serdes for SCH_SHEET_PATH <> SheetPath
  48. return true;
  49. //wxString currentPath = m_frame->GetCurrentSheet().PathAsString();
  50. //return 0 == aDocument.sheet_path().compare( currentPath.ToStdString() );
  51. }
  52. HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_SCH::handleGetOpenDocuments(
  53. const HANDLER_CONTEXT<GetOpenDocuments>& aCtx )
  54. {
  55. if( aCtx.Request.type() != DocumentType::DOCTYPE_SCHEMATIC )
  56. {
  57. ApiResponseStatus e;
  58. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  59. e.set_status( ApiStatusCode::AS_UNHANDLED );
  60. return tl::unexpected( e );
  61. }
  62. GetOpenDocumentsResponse response;
  63. common::types::DocumentSpecifier doc;
  64. wxFileName fn( m_frame->GetCurrentFileName() );
  65. doc.set_type( DocumentType::DOCTYPE_SCHEMATIC );
  66. doc.set_board_filename( fn.GetFullName() );
  67. response.mutable_documents()->Add( std::move( doc ) );
  68. return response;
  69. }
  70. HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> API_HANDLER_SCH::createItemForType( KICAD_T aType,
  71. EDA_ITEM* aContainer )
  72. {
  73. if( !aContainer )
  74. {
  75. ApiResponseStatus e;
  76. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  77. e.set_error_message( "Tried to create an item in a null container" );
  78. return tl::unexpected( e );
  79. }
  80. if( aType == SCH_PIN_T && !dynamic_cast<SCH_SYMBOL*>( aContainer ) )
  81. {
  82. ApiResponseStatus e;
  83. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  84. e.set_error_message( fmt::format( "Tried to create a pin in {}, which is not a symbol",
  85. aContainer->GetFriendlyName().ToStdString() ) );
  86. return tl::unexpected( e );
  87. }
  88. else if( aType == SCH_SYMBOL_T && !dynamic_cast<SCHEMATIC*>( aContainer ) )
  89. {
  90. ApiResponseStatus e;
  91. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  92. e.set_error_message( fmt::format( "Tried to create a symbol in {}, which is not a "
  93. "schematic",
  94. aContainer->GetFriendlyName().ToStdString() ) );
  95. return tl::unexpected( e );
  96. }
  97. std::unique_ptr<EDA_ITEM> created = CreateItemForType( aType, aContainer );
  98. if( !created )
  99. {
  100. ApiResponseStatus e;
  101. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  102. e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
  103. magic_enum::enum_name( aType ) ) );
  104. return tl::unexpected( e );
  105. }
  106. return created;
  107. }
  108. HANDLER_RESULT<ItemRequestStatus> API_HANDLER_SCH::handleCreateUpdateItemsInternal( bool aCreate,
  109. const std::string& aClientName,
  110. const types::ItemHeader &aHeader,
  111. const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
  112. std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
  113. {
  114. ApiResponseStatus e;
  115. auto containerResult = validateItemHeaderDocument( aHeader );
  116. if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
  117. {
  118. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  119. e.set_status( ApiStatusCode::AS_UNHANDLED );
  120. return tl::unexpected( e );
  121. }
  122. else if( !containerResult )
  123. {
  124. e.CopyFrom( containerResult.error() );
  125. return tl::unexpected( e );
  126. }
  127. SCH_SCREEN* screen = m_frame->GetScreen();
  128. EE_RTREE& screenItems = screen->Items();
  129. std::map<KIID, EDA_ITEM*> itemUuidMap;
  130. std::for_each( screenItems.begin(), screenItems.end(),
  131. [&]( EDA_ITEM* aItem )
  132. {
  133. itemUuidMap[aItem->m_Uuid] = aItem;
  134. } );
  135. EDA_ITEM* container = nullptr;
  136. if( containerResult->has_value() )
  137. {
  138. const KIID& containerId = **containerResult;
  139. if( itemUuidMap.count( containerId ) )
  140. {
  141. container = itemUuidMap.at( containerId );
  142. if( !container )
  143. {
  144. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  145. e.set_error_message( fmt::format(
  146. "The requested container {} is not a valid schematic item container",
  147. containerId.AsStdString() ) );
  148. return tl::unexpected( e );
  149. }
  150. }
  151. else
  152. {
  153. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  154. e.set_error_message( fmt::format(
  155. "The requested container {} does not exist in this document",
  156. containerId.AsStdString() ) );
  157. return tl::unexpected( e );
  158. }
  159. }
  160. COMMIT* commit = getCurrentCommit( aClientName );
  161. for( const google::protobuf::Any& anyItem : aItems )
  162. {
  163. ItemStatus status;
  164. std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
  165. if( !type )
  166. {
  167. status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
  168. status.set_error_message( fmt::format( "Could not decode a valid type from {}",
  169. anyItem.type_url() ) );
  170. aItemHandler( status, anyItem );
  171. continue;
  172. }
  173. HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> creationResult =
  174. createItemForType( *type, container );
  175. if( !creationResult )
  176. {
  177. status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
  178. status.set_error_message( creationResult.error().error_message() );
  179. aItemHandler( status, anyItem );
  180. continue;
  181. }
  182. std::unique_ptr<EDA_ITEM> item( std::move( *creationResult ) );
  183. if( !item->Deserialize( anyItem ) )
  184. {
  185. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  186. e.set_error_message( fmt::format( "could not unpack {} from request",
  187. item->GetClass().ToStdString() ) );
  188. return tl::unexpected( e );
  189. }
  190. if( aCreate && itemUuidMap.count( item->m_Uuid ) )
  191. {
  192. status.set_code( ItemStatusCode::ISC_EXISTING );
  193. status.set_error_message( fmt::format( "an item with UUID {} already exists",
  194. item->m_Uuid.AsStdString() ) );
  195. aItemHandler( status, anyItem );
  196. continue;
  197. }
  198. else if( !aCreate && !itemUuidMap.count( item->m_Uuid ) )
  199. {
  200. status.set_code( ItemStatusCode::ISC_NONEXISTENT );
  201. status.set_error_message( fmt::format( "an item with UUID {} does not exist",
  202. item->m_Uuid.AsStdString() ) );
  203. aItemHandler( status, anyItem );
  204. continue;
  205. }
  206. status.set_code( ItemStatusCode::ISC_OK );
  207. google::protobuf::Any newItem;
  208. if( aCreate )
  209. {
  210. item->Serialize( newItem );
  211. commit->Add( item.release() );
  212. if( !m_activeClients.count( aClientName ) )
  213. pushCurrentCommit( aClientName, _( "Added items via API" ) );
  214. }
  215. else
  216. {
  217. EDA_ITEM* edaItem = itemUuidMap[item->m_Uuid];
  218. if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( edaItem ) )
  219. {
  220. schItem->SwapItemData( static_cast<SCH_ITEM*>( item.get() ) );
  221. schItem->Serialize( newItem );
  222. commit->Modify( schItem );
  223. }
  224. else
  225. {
  226. wxASSERT( false );
  227. }
  228. if( !m_activeClients.count( aClientName ) )
  229. pushCurrentCommit( aClientName, _( "Created items via API" ) );
  230. }
  231. aItemHandler( status, newItem );
  232. }
  233. return ItemRequestStatus::IRS_OK;
  234. }
  235. void API_HANDLER_SCH::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
  236. const std::string& aClientName )
  237. {
  238. // TODO
  239. }
  240. std::optional<EDA_ITEM*> API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument,
  241. const KIID& aId )
  242. {
  243. if( !validateDocument( aDocument ) )
  244. return std::nullopt;
  245. // TODO
  246. return std::nullopt;
  247. }