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 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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <fmt/format.h>
22#include <wx/app.h>
23#include <wx/datetime.h>
24#include <wx/event.h>
25#include <wx/stdpaths.h>
26
27#include <advanced_config.h>
28#include <api/api_handler.h>
29#include <api/api_utils.h> // traceApi
30#include <api/api_server.h>
31#include <kiid.h>
32#include <kinng.h>
33#include <paths.h>
34#include <pgm_base.h>
36#include <string_utils.h>
37
38#include <api/common/envelope.pb.h>
39
40#ifdef __UNIX__
41#include <sys/file.h>
42#endif
43
44using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode;
45
46
47wxString KICAD_API_SERVER::s_logFileName = "api.log";
48
49
50wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
51
52
54 wxEvtHandler(),
55 m_token( KIID().AsStdString() ),
56 m_readyToReply( false )
57{
58 if( !aAutoStart )
59 return;
60
61 if( !Pgm().GetCommonSettings()->m_Api.enable_server )
62 {
63 wxLogTrace( traceApi, "Server: disabled by user preferences." );
64 return;
65 }
66
67 Start();
68}
69
70
75
76
78{
79 wxFileName socket;
80
81#ifdef __WXMAC__
82 socket.AssignDir( wxS( "/tmp" ) );
83#else
84 socket.AssignDir( wxStandardPaths::Get().GetTempDir() );
85#endif
86
87 socket.AppendDir( wxS( "kicad" ) );
88 socket.SetFullName( wxS( "api.sock" ) );
89
90 return socket;
91}
92
93
95{
96 return fmt::format( "ipc://{}", StandardSocketPath().GetFullPath().ToUTF8().data() );
97}
98
99
101{
102 if( Running() )
103 return;
104
105 wxFileName socket;
106
107 if( m_socketPathOverride.IsEmpty() )
108 {
109 socket = StandardSocketPath();
110 }
111 else
112 {
113 socket.Assign( m_socketPathOverride );
114
115 if( !socket.IsAbsolute() )
116 socket.MakeAbsolute();
117 }
118
119 if( !PATHS::EnsurePathExists( socket.GetPath() ) )
120 {
121 wxLogTrace( traceApi, wxString::Format( "Server: socket path %s could not be created",
122 socket.GetPath() ) );
123 return;
124 }
125
126#ifndef __WINDOWS__
127 // We use non-abstract sockets because macOS and some other non-Linux platforms don't support
128 // abstract sockets, which means there might be an old socket to unlink. In order to try to
129 // recover this, we lock a file (which will be unlocked on process exit) and if we get the lock,
130 // we know the old socket is orphaned and can be removed.
131 wxFileName lockFilePath( socket.GetPath(), wxS( "api.lock" ) );
132
133 int lockFile = open( lockFilePath.GetFullPath().c_str(), O_RDONLY | O_CREAT, 0600 );
134
135 if( lockFile >= 0 && flock( lockFile, LOCK_EX | LOCK_NB ) == 0 )
136 {
137 if( socket.Exists() )
138 {
139 wxLogTrace( traceApi, wxString::Format( "Server: cleaning up stale socket path %s",
140 socket.GetFullPath() ) );
141 wxRemoveFile( socket.GetFullPath() );
142 }
143 }
144#endif
145
146 if( socket.Exists() )
147 {
148 socket.SetFullName( wxString::Format( wxS( "api-%lu.sock" ), ::wxGetProcessId() ) );
149
150 if( socket.Exists() )
151 {
152 wxLogTrace( traceApi, wxString::Format( "Server: PID socket path %s already exists!",
153 socket.GetFullPath() ) );
154 return;
155 }
156 }
157
158 m_server = std::make_unique<KINNG_REQUEST_SERVER>(
159 fmt::format( "ipc://{}", socket.GetFullPath().ToStdString() ) );
160 m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } );
161
162 if( !m_server->Start() )
163 {
164 wxLogTrace( traceApi, "Server: failed to start KINNG listener thread" );
165 m_server.reset( nullptr );
166 return;
167 }
168
169 m_logFilePath.AssignDir( PATHS::GetLogsPath() );
170 m_logFilePath.SetName( s_logFileName );
171
172 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
173 {
175 log( fmt::format( "--- KiCad API server started at {} ---\n", SocketPath() ) );
176 }
177
178 wxLogTrace( traceApi, wxString::Format( "Server: listening at %s", SocketPath() ) );
179 Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
180}
181
182
184{
185 if( !Running() )
186 return;
187
188 wxLogTrace( traceApi, "Stopping server" );
189 Unbind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
190
191 m_server->Stop();
192 m_server.reset( nullptr );
193}
194
195
197{
198 return m_server && m_server->Running();
199}
200
201
203{
204 wxCHECK( aHandler, /* void */ );
205
206 aHandler->setApiServer( this );
207 m_handlers.insert( aHandler );
208}
209
210
212{
213 if( aHandler )
214 aHandler->setApiServer( nullptr );
215
216 m_handlers.erase( aHandler );
217}
218
219
221{
222 for( API_HANDLER* handler : m_handlers )
223 handler->onNetSettingsChanged();
224}
225
226
227void KICAD_API_SERVER::NotifyDocumentRenamed( kiapi::common::types::DocumentType aType,
228 const wxString& aOldPath, const wxString& aNewPath )
229{
230 if( m_renameListener )
231 m_renameListener( aType, aOldPath, aNewPath );
232}
233
234
236{
237 return m_server ? m_server->SocketPath() : "";
238}
239
240
241void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
242{
243 if( !m_readyToReply.load( std::memory_order_acquire ) )
244 {
245 ApiResponse notHandled;
246 notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY );
247 notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" );
248 m_server->Reply( notHandled.SerializeAsString() );
249 log( "Got incoming request but was not yet ready to reply." );
250 return;
251 }
252
253 wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT );
254
255 // We don't actually need write access to this string, but client data is non-const
256 evt->SetClientData( static_cast<void*>( aRequest ) );
257
258 // Takes ownership and frees the wxCommandEvent
259 QueueEvent( evt );
260}
261
262
263void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
264{
265 std::string& requestString = *static_cast<std::string*>( aEvent.GetClientData() );
266 handleApiRequestString( requestString );
267}
268
269
270void KICAD_API_SERVER::handleApiRequestString( std::string& aRequestString )
271{
272 ApiRequest request;
273
274 if( !request.ParseFromString( aRequestString ) )
275 {
276 ApiResponse error;
277 error.mutable_header()->set_kicad_token( m_token );
278 error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
279 error.mutable_status()->set_error_message( "request could not be parsed" );
280 m_server->Reply( error.SerializeAsString() );
281
282 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
283 log( "Response (ERROR): " + error.Utf8DebugString() );
284
285 return;
286 }
287
288 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
289 log( "Request: " + request.Utf8DebugString() );
290
291 if( !request.header().kicad_token().empty() &&
292 request.header().kicad_token().compare( m_token ) != 0 )
293 {
294 ApiResponse error;
295 error.mutable_header()->set_kicad_token( m_token );
296 error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH );
297 error.mutable_status()->set_error_message(
298 "the provided kicad_token did not match this KiCad instance's token" );
299 m_server->Reply( error.SerializeAsString() );
300
301 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
302 log( "Response (ERROR): " + error.Utf8DebugString() );
303
304 return;
305 }
306
308 bool notifyNetSettings = false;
309
310 for( API_HANDLER* handler : m_handlers )
311 {
312 result = handler->Handle( request );
313 notifyNetSettings |= handler->clearNetSettingsNotification();
314
315 if( result.has_value() )
316 break;
317 else if( result.error().status() != ApiStatusCode::AS_UNHANDLED )
318 break;
319 }
320
321 if( notifyNetSettings )
323
324 // Note: at the point we call Reply(), we no longer own requestString.
325
326 if( result.has_value() )
327 {
328 result->mutable_header()->set_kicad_token( m_token );
329 m_server->Reply( result->SerializeAsString() );
330
331 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
332 log( "Response: " + result->Utf8DebugString() );
333 }
334 else
335 {
336 ApiResponse error;
337 error.mutable_status()->CopyFrom( result.error() );
338 error.mutable_header()->set_kicad_token( m_token );
339
340 if( result.error().status() == ApiStatusCode::AS_UNHANDLED )
341 {
342 std::string type = "<unparseable Any>";
343 google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type );
344 std::string msg = fmt::format( "no handler available for request of type {}", type );
345 error.mutable_status()->set_error_message( msg );
346 }
347
348 m_server->Reply( error.SerializeAsString() );
349
350 if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
351 log( "Response (ERROR): " + error.Utf8DebugString() );
352 }
353}
354
355
356void KICAD_API_SERVER::log( const std::string& aOutput )
357{
358 FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) );
359
360 if( !fp )
361 return;
362
363 wxString out;
364 wxDateTime now = wxDateTime::Now();
365
366 fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ),
367 now.FormatISOCombined(), aOutput ) ) );
368 fclose( fp );
369}
tl::expected< ApiResponse, ApiResponseStatus > API_RESULT
Definition api_handler.h:44
wxDEFINE_EVENT(API_REQUEST_EVENT, wxCommandEvent)
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void setApiServer(KICAD_API_SERVER *aServer)
Set by KICAD_API_SERVER::RegisterHandler; null when the handler is not registered.
Definition api_handler.h:89
std::string m_token
Definition api_server.h:143
bool Running() const
void handleApiEvent(wxCommandEvent &aEvent)
Event handler that receives the event on the main thread sent by onApiRequest.
wxFileName m_logFilePath
Definition api_server.h:153
std::function< void(kiapi::common::types::DocumentType, const wxString &, const wxString &)> m_renameListener
Definition api_server.h:147
static wxString s_logFileName
Definition api_server.h:151
void RegisterHandler(API_HANDLER *aHandler)
Adds a new request handler to the server.
void onApiRequest(std::string *aRequest)
Callback that executes on the server thread and generates an event that will be handled by the wxWidg...
wxString m_socketPathOverride
Definition api_server.h:149
std::set< API_HANDLER * > m_handlers
Definition api_server.h:141
void NotifyDocumentRenamed(kiapi::common::types::DocumentType aType, const wxString &aOldPath, const wxString &aNewPath)
Notifies the host application that a document was saved to a new path.
std::atomic< bool > m_readyToReply
Definition api_server.h:145
std::string SocketPath() const
void handleApiRequestString(std::string &aRequestString)
static std::string StandardSocketUrl()
Return the default API socket URL (including the ipc:// scheme).
void log(const std::string &aOutput)
KICAD_API_SERVER(bool aAutoStart=true)
void DeregisterHandler(API_HANDLER *aHandler)
static wxFileName StandardSocketPath()
Return the default API socket path (without the ipc:// scheme).
void NotifyNetSettingsChanged()
Calls onNetSettingsChanged() on every registered handler, so that editors can update state derived fr...
std::unique_ptr< KINNG_REQUEST_SERVER > m_server
Definition api_server.h:139
Definition kiid.h:46
static wxString GetLogsPath()
Gets a path to use for user-visible log files.
Definition paths.cpp:497
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:508
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
Definition api_utils.cpp:33
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
wxString result
Test unit parsing edge cases and error handling.