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.

355 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(
  38. const HANDLER_CONTEXT<BeginCommit>& 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(
  59. const HANDLER_CONTEXT<EndCommit>& 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( aCtx.Request.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( aCtx.Request.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. aCtx.Request.id().value() ) );
  94. return tl::unexpected( e );
  95. }
  96. pushCurrentCommit( aCtx.ClientName, wxString( aCtx.Request.message().c_str(), wxConvUTF8 ) );
  97. break;
  98. }
  99. default:
  100. break;
  101. }
  102. return response;
  103. }
  104. COMMIT* API_HANDLER_EDITOR::getCurrentCommit( const std::string& aClientName )
  105. {
  106. if( !m_commits.count( aClientName ) )
  107. {
  108. KIID id;
  109. m_commits[aClientName] = std::make_pair( id, createCommit() );
  110. }
  111. return m_commits.at( aClientName ).second.get();
  112. }
  113. void API_HANDLER_EDITOR::pushCurrentCommit( const std::string& aClientName,
  114. const wxString& aMessage )
  115. {
  116. auto it = m_commits.find( aClientName );
  117. if( it == m_commits.end() )
  118. return;
  119. it->second.second->Push( aMessage.IsEmpty() ? m_defaultCommitMessage : aMessage );
  120. m_commits.erase( it );
  121. m_activeClients.erase( aClientName );
  122. }
  123. HANDLER_RESULT<bool> API_HANDLER_EDITOR::validateDocument( const 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 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(
  175. const HANDLER_CONTEXT<CreateItems>& aCtx )
  176. {
  177. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  178. return tl::unexpected( *busy );
  179. CreateItemsResponse response;
  180. HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( true,
  181. aCtx.ClientName,
  182. aCtx.Request.header(), aCtx.Request.items(),
  183. [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
  184. {
  185. ItemCreationResult itemResult;
  186. itemResult.mutable_status()->CopyFrom( aStatus );
  187. itemResult.mutable_item()->CopyFrom( aItem );
  188. response.mutable_created_items()->Add( std::move( itemResult ) );
  189. } );
  190. if( !result.has_value() )
  191. return tl::unexpected( result.error() );
  192. response.set_status( *result );
  193. return response;
  194. }
  195. HANDLER_RESULT<UpdateItemsResponse> API_HANDLER_EDITOR::handleUpdateItems(
  196. const HANDLER_CONTEXT<UpdateItems>& aCtx )
  197. {
  198. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  199. return tl::unexpected( *busy );
  200. UpdateItemsResponse response;
  201. HANDLER_RESULT<ItemRequestStatus> result = handleCreateUpdateItemsInternal( false,
  202. aCtx.ClientName,
  203. aCtx.Request.header(), aCtx.Request.items(),
  204. [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
  205. {
  206. ItemUpdateResult itemResult;
  207. itemResult.mutable_status()->CopyFrom( aStatus );
  208. itemResult.mutable_item()->CopyFrom( aItem );
  209. response.mutable_updated_items()->Add( std::move( itemResult ) );
  210. } );
  211. if( !result.has_value() )
  212. return tl::unexpected( result.error() );
  213. response.set_status( *result );
  214. return response;
  215. }
  216. HANDLER_RESULT<DeleteItemsResponse> API_HANDLER_EDITOR::handleDeleteItems(
  217. const HANDLER_CONTEXT<DeleteItems>& aCtx )
  218. {
  219. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  220. return tl::unexpected( *busy );
  221. if( !validateItemHeaderDocument( aCtx.Request.header() ) )
  222. {
  223. ApiResponseStatus e;
  224. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  225. e.set_status( ApiStatusCode::AS_UNHANDLED );
  226. return tl::unexpected( e );
  227. }
  228. std::map<KIID, ItemDeletionStatus> itemsToDelete;
  229. for( const kiapi::common::types::KIID& kiidBuf : aCtx.Request.item_ids() )
  230. {
  231. if( !kiidBuf.value().empty() )
  232. {
  233. KIID kiid( kiidBuf.value() );
  234. itemsToDelete[kiid] = ItemDeletionStatus::IDS_NONEXISTENT;
  235. }
  236. }
  237. if( itemsToDelete.empty() )
  238. {
  239. ApiResponseStatus e;
  240. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  241. e.set_error_message( "no valid items to delete were given" );
  242. return tl::unexpected( e );
  243. }
  244. deleteItemsInternal( itemsToDelete, aCtx.ClientName );
  245. DeleteItemsResponse response;
  246. for( const auto& [id, status] : itemsToDelete )
  247. {
  248. ItemDeletionResult result;
  249. result.mutable_id()->set_value( id.AsStdString() );
  250. result.set_status( status );
  251. }
  252. response.set_status( kiapi::common::types::ItemRequestStatus::IRS_OK );
  253. return response;
  254. }
  255. HANDLER_RESULT<HitTestResponse> API_HANDLER_EDITOR::handleHitTest(
  256. const HANDLER_CONTEXT<HitTest>& aCtx )
  257. {
  258. if( std::optional<ApiResponseStatus> busy = checkForBusy() )
  259. return tl::unexpected( *busy );
  260. if( !validateItemHeaderDocument( aCtx.Request.header() ) )
  261. {
  262. ApiResponseStatus e;
  263. // No message needed for AS_UNHANDLED; this is an internal flag for the API server
  264. e.set_status( ApiStatusCode::AS_UNHANDLED );
  265. return tl::unexpected( e );
  266. }
  267. HitTestResponse response;
  268. std::optional<EDA_ITEM*> item = getItemFromDocument( aCtx.Request.header().document(),
  269. KIID( aCtx.Request.id().value() ) );
  270. if( !item )
  271. {
  272. ApiResponseStatus e;
  273. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  274. e.set_error_message( "the requested item ID is not present in the given document" );
  275. return tl::unexpected( e );
  276. }
  277. if( ( *item )->HitTest( UnpackVector2( aCtx.Request.position() ), aCtx.Request.tolerance() ) )
  278. response.set_result( HitTestResult::HTR_HIT );
  279. else
  280. response.set_result( HitTestResult::HTR_NO_HIT );
  281. return response;
  282. }