KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_server.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 Jon Evans <[email protected]>
5 * Copyright (C) 2023 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
21#include <wx/app.h>
22#include <wx/datetime.h>
23#include <wx/event.h>
24#include <wx/stdpaths.h>
25
26#include <advanced_config.h>
27#include <api/api_plugin_manager.h> // traceApi
28#include <api/api_server.h>
30#include <kiid.h>
31#include <kinng.h>
32#include <paths.h>
33#include <pgm_base.h>
35#include <string_utils.h>
36
37#include <api/common/envelope.pb.h>
38
39using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode;
40
41
42wxString KICAD_API_SERVER::s_logFileName = "api.log";
43
44
45wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
46
47
49 wxEvtHandler(),
50 m_token( KIID().AsStdString() ),
51 m_readyToReply( false )
52{
53 m_commonHandler = std::make_unique<API_HANDLER_COMMON>();
55
56 if( !Pgm().GetCommonSettings()->m_Api.enable_server )
57 {
58 wxLogTrace( traceApi, "Server: disabled by user preferences." );
59 return;
60 }
61
62 Start();
63}
64
65
67{
68}
69
70
72{
73 if( Running() )
74 return;
75
76 wxFileName socket;
77#ifdef __WXMAC__
78 socket.AssignDir( wxS( "/tmp" ) );
79#else
80 socket.AssignDir( wxStandardPaths::Get().GetTempDir() );
81#endif
82 socket.AppendDir( wxS( "kicad" ) );
83 socket.SetFullName( wxS( "api.sock" ) );
84
85 if( !PATHS::EnsurePathExists( socket.GetPath() ) )
86 {
87 wxLogTrace( traceApi, wxString::Format( "Server: socket path %s could not be created",
88 socket.GetPath() ) );
89 return;
90 }
91
92 if( socket.FileExists() )
93 {
94 socket.SetFullName( wxString::Format( wxS( "api-%ul.sock" ), ::wxGetProcessId() ) );
95
96 if( socket.FileExists() )
97 {
98 wxLogTrace( traceApi, wxString::Format( "Server: PID socket path %s already exists!",
99 socket.GetFullPath() ) );
100 return;
101 }
102 }
103
104 m_server = std::make_unique<KINNG_REQUEST_SERVER>(
105 fmt::format( "ipc://{}", socket.GetFullPath().ToStdString() ) );
106 m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } );
107
108 m_logFilePath.AssignDir( PATHS::GetLogsPath() );
109 m_logFilePath.SetName( s_logFileName );
110
112 {
114 log( fmt::format( "--- KiCad API server started at {} ---\n", SocketPath() ) );
115 }
116
117 wxLogTrace( traceApi, wxString::Format( "Server: listening at %s", SocketPath() ) );
118
119 Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
120}
121
122
124{
125 if( !Running() )
126 return;
127
128 wxLogTrace( traceApi, "Stopping server" );
129 Unbind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
130
131 m_server->Stop();
132 m_server.reset( nullptr );
133}
134
135
137{
138 return m_server && m_server->Running();
139}
140
141
143{
144 wxCHECK( aHandler, /* void */ );
145 m_handlers.insert( aHandler );
146}
147
148
150{
151 m_handlers.erase( aHandler );
152}
153
154
156{
157 return m_server ? m_server->SocketPath() : "";
158}
159
160
161void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
162{
163 if( !m_readyToReply )
164 {
165 ApiResponse notHandled;
166 notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY );
167 notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" );
168 m_server->Reply( notHandled.SerializeAsString() );
169 log( "Got incoming request but was not yet ready to reply." );
170 return;
171 }
172
173 wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT );
174
175 // We don't actually need write access to this string, but client data is non-const
176 evt->SetClientData( static_cast<void*>( aRequest ) );
177
178 // Takes ownership and frees the wxCommandEvent
179 QueueEvent( evt );
180}
181
182
183void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
184{
185 std::string& requestString = *static_cast<std::string*>( aEvent.GetClientData() );
186 ApiRequest request;
187
188 if( !request.ParseFromString( requestString ) )
189 {
190 ApiResponse error;
191 error.mutable_header()->set_kicad_token( m_token );
192 error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
193 error.mutable_status()->set_error_message( "request could not be parsed" );
194 m_server->Reply( error.SerializeAsString() );
195
197 log( "Response (ERROR): " + error.Utf8DebugString() );
198 }
199
201 log( "Request: " + request.Utf8DebugString() );
202
203 if( !request.header().kicad_token().empty() &&
204 request.header().kicad_token().compare( m_token ) != 0 )
205 {
206 ApiResponse error;
207 error.mutable_header()->set_kicad_token( m_token );
208 error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH );
209 error.mutable_status()->set_error_message(
210 "the provided kicad_token did not match this KiCad instance's token" );
211 m_server->Reply( error.SerializeAsString() );
212
214 log( "Response (ERROR): " + error.Utf8DebugString() );
215 }
216
217 API_RESULT result;
218
219 for( API_HANDLER* handler : m_handlers )
220 {
221 result = handler->Handle( request );
222
223 if( result.has_value() )
224 break;
225 else if( result.error().status() != ApiStatusCode::AS_UNHANDLED )
226 break;
227 }
228
229 // Note: at the point we call Reply(), we no longer own requestString.
230
231 if( result.has_value() )
232 {
233 result->mutable_header()->set_kicad_token( m_token );
234 m_server->Reply( result->SerializeAsString() );
235
237 log( "Response: " + result->Utf8DebugString() );
238 }
239 else
240 {
241 ApiResponse error;
242 error.mutable_status()->CopyFrom( result.error() );
243 error.mutable_header()->set_kicad_token( m_token );
244
245 if( result.error().status() == ApiStatusCode::AS_UNHANDLED )
246 {
247 std::string type = "<unparseable Any>";
248 google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type );
249 std::string msg = fmt::format( "no handler available for request of type {}", type );
250 error.mutable_status()->set_error_message( msg );
251 }
252
253 m_server->Reply( error.SerializeAsString() );
254
255 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
256 log( "Response (ERROR): " + error.Utf8DebugString() );
257 }
258}
259
260
261void KICAD_API_SERVER::log( const std::string& aOutput )
262{
263 FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) );
264
265 if( !fp )
266 return;
267
268 wxString out;
269 wxDateTime now = wxDateTime::Now();
270
271 fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ),
272 now.FormatISOCombined(), aOutput ) ) );
273 fclose( fp );
274}
tl::expected< ApiResponse, ApiResponseStatus > API_RESULT
Definition: api_handler.h:42
wxDEFINE_EVENT(API_REQUEST_EVENT, wxCommandEvent)
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
std::string m_token
Definition: api_server.h:98
bool Running() const
Definition: api_server.cpp:136
void handleApiEvent(wxCommandEvent &aEvent)
Event handler that receives the event on the main thread sent by onApiRequest.
Definition: api_server.cpp:183
wxFileName m_logFilePath
Definition: api_server.h:106
std::unique_ptr< API_HANDLER_COMMON > m_commonHandler
Definition: api_server.h:102
static wxString s_logFileName
Definition: api_server.h:104
void RegisterHandler(API_HANDLER *aHandler)
Adds a new request handler to the server.
Definition: api_server.cpp:142
void onApiRequest(std::string *aRequest)
Callback that executes on the server thread and generates an event that will be handled by the wxWidg...
Definition: api_server.cpp:161
std::set< API_HANDLER * > m_handlers
Definition: api_server.h:96
std::string SocketPath() const
Definition: api_server.cpp:155
void log(const std::string &aOutput)
Definition: api_server.cpp:261
void DeregisterHandler(API_HANDLER *aHandler)
Definition: api_server.cpp:149
std::unique_ptr< KINNG_REQUEST_SERVER > m_server
Definition: api_server.h:94
Definition: kiid.h:49
static wxString GetLogsPath()
Gets a path to use for user-visible log files.
Definition: paths.cpp:391
static bool EnsurePathExists(const wxString &aPath)
Attempts to create a given path if it does not exist.
Definition: paths.cpp:402
bool m_EnableAPILogging
Log IPC API requests and responses.
const wxChar *const traceApi
Flag to enable debug output related to the API plugin system.
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: pgm_base.cpp:1059
see class PGM_BASE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:391