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.

353 lines
11 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 (C) 2024 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_editor.h>
  21. #include <api/api_utils.h>
  22. #include <eda_base_frame.h>
  23. #include <eda_item.h>
  24. #include <wx/wx.h>
  25. using namespace kiapi::common::commands;
  26. API_HANDLER_EDITOR::API_HANDLER_EDITOR( EDA_BASE_FRAME* aFrame ) :
  27. API_HANDLER(),
  28. m_frame( aFrame )
  29. {
  30. registerHandler<BeginCommit, BeginCommitResponse>( &API_HANDLER_EDITOR::handleBeginCommit );
  31. registerHandler<EndCommit, EndCommitResponse>( &API_HANDLER_EDITOR::handleEndCommit );
  32. registerHandler<CreateItems, CreateItemsResponse>( &API_HANDLER_EDITOR::handleCreateItems );
  33. registerHandler<UpdateItems, UpdateItemsResponse>( &API_HANDLER_EDITOR::handleUpdateItems );
  34. registerHandler<DeleteItems, DeleteItemsResponse>( &API_HANDLER_EDITOR::handleDeleteItems );
  35. registerHandler<HitTest, HitTestResponse>( &API_HANDLER_EDITOR::handleHitTest );
  36. }
  37. HANDLER_RESULT<BeginCommitResponse> API_HANDLER_EDITOR::handleBeginCommit( BeginCommit& aMsg,
  38. const HANDLER_CONTEXT& aCtx )
  39. {
  40. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  41. return tl::unexpected( *busy );
  42. if( m_commits.count( aCtx.ClientName ) )
  43. {
  44. ApiResponseStatus e;
  45. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  46. e.set_error_message( fmt::format( "the client {} already has a commit in progress",
  47. aCtx.ClientName ) );
  48. return tl::unexpected( e );
  49. }
  50. wxASSERT( !m_activeClients.count( aCtx.ClientName ) );
  51. BeginCommitResponse response;
  52. KIID id;
  53. m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() );
  54. response.mutable_id()->set_value( id.AsStdString() );
  55. m_activeClients.insert( aCtx.ClientName );
  56. return response;
  57. }
  58. HANDLER_RESULT<EndCommitResponse> API_HANDLER_EDITOR::handleEndCommit( EndCommit& aMsg,
  59. const HANDLER_CONTEXT& aCtx )
  60. {
  61. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  62. return tl::unexpected( *busy );
  63. if( !m_commits.count( aCtx.ClientName ) )
  64. {
  65. ApiResponseStatus e;
  66. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  67. e.set_error_message( fmt::format( "the client {} does not has a commit in progress",
  68. aCtx.ClientName ) );
  69. return tl::unexpected( e );
  70. }
  71. wxASSERT( m_activeClients.count( aCtx.ClientName ) );
  72. const std::pair<KIID, std::unique_ptr<COMMIT>>& pair = m_commits.at( aCtx.ClientName );
  73. const KIID& id = pair.first;
  74. const std::unique_ptr<COMMIT>& commit = pair.second;
  75. EndCommitResponse response;
  76. // Do not check IDs with drop; it is a safety net in case the id was lost on the client side
  77. switch( aMsg.action() )
  78. {
  79. case kiapi::common::commands::CMA_DROP:
  80. {
  81. commit->Revert();
  82. m_commits.erase( aCtx.ClientName );
  83. m_activeClients.erase( aCtx.ClientName );
  84. break;
  85. }
  86. case kiapi::common::commands::CMA_COMMIT:
  87. {
  88. if( aMsg.id().value().compare( id.AsStdString() ) != 0 )
  89. {
  90. ApiResponseStatus e;
  91. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  92. e.set_error_message( fmt::format( "the id {} does not match the commit in progress",
  93. aMsg.id().value() ) );
  94. return tl::unexpected( e );
  95. }
  96. pushCurrentCommit( aCtx, wxString( aMsg.message().c_str(), wxConvUTF8 ) );
  97. break;
  98. }
  99. default:
  100. break;
  101. }
  102. return response;
  103. }
  104. COMMIT* API_HANDLER_EDITOR::getCurrentCommit( const HANDLER_CONTEXT& aCtx )
  105. {
  106. if( !m_commits.count( aCtx.ClientName ) )
  107. {
  108. KIID id;
  109. m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() );
  110. }
  111. return m_commits.at( aCtx.ClientName ).second.get();
  112. }
  113. void API_HANDLER_EDITOR::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage )
  114. {
  115. auto it = m_commits.find( aCtx.ClientName );
  116. if( it == m_commits.end() )
  117. return;
  118. it->second.second->Push( aMessage.IsEmpty() ? m_defaultCommitMessage : aMessage );
  119. m_commits.erase( it );
  120. m_activeClients.erase( aCtx.ClientName );
  121. }
  122. HANDLER_RESULT<bool> API_HANDLER_EDITOR::validateDocument(
  123. const kiapi::common::types::DocumentSpecifier& aDocument )
  124. {
  125. if( !validateDocumentInternal( aDocument ) )
  126. {
  127. ApiResponseStatus e;
  128. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  129. e.set_error_message( fmt::format( "the requested document {} is not open",
  130. aDocument.board_filename() ) );
  131. return tl::unexpected( e );
  132. }
  133. return true;
  134. }
  135. HANDLER_RESULT<std::optional<KIID>> API_HANDLER_EDITOR::validateItemHeaderDocument(
  136. const kiapi::common::types::ItemHeader& aHeader )
  137. {
  138. if( !aHeader.has_document() || aHeader.document().type() != thisDocumentType() )
  139. {
  140. ApiResponseStatus e;
  141. e.set_status( ApiStatusCode::AS_UNHANDLED );
  142. // No error message, this is a flag that the server should try a different handler
  143. return tl::unexpected( e );
  144. }
  145. HANDLER_RESULT<bool> documentValidation = validateDocument( aHeader.document() );
  146. if( !documentValidation )
  147. return tl::unexpected( documentValidation.error() );
  148. if( !validateDocumentInternal( aHeader.document() ) )
  149. {
  150. ApiResponseStatus e;
  151. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  152. e.set_error_message( fmt::format( "the requested document {} is not open",
  153. aHeader.document().board_filename() ) );
  154. return tl::unexpected( e );
  155. }
  156. if( aHeader.has_container() )
  157. {
  158. return KIID( aHeader.container().value() );
  159. }
  160. // Valid header, but no container provided
  161. return std::nullopt;
  162. }
  163. std::optional<ApiResponseStatus> API_HANDLER_EDITOR::checkForBusy()
  164. {
  165. if( !m_frame->CanAcceptApiCommands() )
  166. {
  167. ApiResponseStatus e;
  168. e.set_status( ApiStatusCode::AS_BUSY );
  169. e.set_error_message( "KiCad is busy and cannot respond to API requests right now" );
  170. return e;
  171. }
  172. return std::nullopt;
  173. }
  174. HANDLER_RESULT<CreateItemsResponse> API_HANDLER_EDITOR::handleCreateItems( CreateItems& aMsg,
  175. const HANDLER_CONTEXT& aCtx )
  176. {
  177. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  178. return tl::unexpected( *busy );
  179. CreateItemsResponse response;
  180. HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( true, aCtx,
  181. aMsg.header(), aMsg.items(),
  182. [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
  183. {
  184. ItemCreationResult itemResult;
  185. itemResult.mutable_status()->CopyFrom( aStatus );
  186. itemResult.mutable_item()->CopyFrom( aItem );
  187. response.mutable_created_items()->Add( std::move( itemResult ) );
  188. } );
  189. if( !result.has_value() )
  190. return tl::unexpected( result.error() );
  191. response.set_status( *result );
  192. return response;
  193. }
  194. HANDLER_RESULT<UpdateItemsResponse> API_HANDLER_EDITOR::handleUpdateItems( UpdateItems& aMsg,
  195. const HANDLER_CONTEXT& aCtx )
  196. {
  197. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  198. return tl::unexpected( *busy );
  199. UpdateItemsResponse response;
  200. HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( false, aCtx,
  201. aMsg.header(), aMsg.items(),
  202. [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
  203. {
  204. ItemUpdateResult itemResult;
  205. itemResult.mutable_status()->CopyFrom( aStatus );
  206. itemResult.mutable_item()->CopyFrom( aItem );
  207. response.mutable_updated_items()->Add( std::move( itemResult ) );
  208. } );
  209. if( !result.has_value() )
  210. return tl::unexpected( result.error() );
  211. response.set_status( *result );
  212. return response;
  213. }
  214. HANDLER_RESULT<DeleteItemsResponse> API_HANDLER_EDITOR::handleDeleteItems( DeleteItems& aMsg,
  215. const HANDLER_CONTEXT& aCtx )
  216. {
  217. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  218. return tl::unexpected( *busy );
  219. if( !validateItemHeaderDocument( aMsg.header() ) )
  220. {
  221. ApiResponseStatus e;
  222. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  223. e.set_status( ApiStatusCode::AS_UNHANDLED );
  224. return tl::unexpected( e );
  225. }
  226. std::map<KIID, ItemDeletionStatus> itemsToDelete;
  227. for( const kiapi::common::types::KIID& kiidBuf : aMsg.item_ids() )
  228. {
  229. if( !kiidBuf.value().empty() )
  230. {
  231. KIID kiid( kiidBuf.value() );
  232. itemsToDelete[kiid] = ItemDeletionStatus::IDS_NONEXISTENT;
  233. }
  234. }
  235. if( itemsToDelete.empty() )
  236. {
  237. ApiResponseStatus e;
  238. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  239. e.set_error_message( "no valid items to delete were given" );
  240. return tl::unexpected( e );
  241. }
  242. deleteItemsInternal( itemsToDelete, aCtx );
  243. DeleteItemsResponse response;
  244. for( const auto& [id, status] : itemsToDelete )
  245. {
  246. ItemDeletionResult result;
  247. result.mutable_id()->set_value( id.AsStdString() );
  248. result.set_status( status );
  249. }
  250. response.set_status( kiapi::common::types::ItemRequestStatus::IRS_OK );
  251. return response;
  252. }
  253. HANDLER_RESULT<HitTestResponse> API_HANDLER_EDITOR::handleHitTest( HitTest& aMsg,
  254. const HANDLER_CONTEXT& aCtx )
  255. {
  256. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  257. return tl::unexpected( *busy );
  258. if( !validateItemHeaderDocument( aMsg.header() ) )
  259. {
  260. ApiResponseStatus e;
  261. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  262. e.set_status( ApiStatusCode::AS_UNHANDLED );
  263. return tl::unexpected( e );
  264. }
  265. HitTestResponse response;
  266. std::optional<EDA_ITEM*> item = getItemFromDocument( aMsg.header().document(),
  267. KIID( aMsg.id().value() ) );
  268. if( !item )
  269. {
  270. ApiResponseStatus e;
  271. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  272. e.set_error_message( "the requested item ID is not present in the given document" );
  273. return tl::unexpected( e );
  274. }
  275. if( ( *item )->HitTest( kiapi::common::UnpackVector2( aMsg.position() ), aMsg.tolerance() ) )
  276. response.set_result( HitTestResult::HTR_HIT );
  277. else
  278. response.set_result( HitTestResult::HTR_NO_HIT );
  279. return response;
  280. }