275 lines
8.0 KiB
C++
275 lines
8.0 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
|
|
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <wx/app.h>
|
|
#include <wx/datetime.h>
|
|
#include <wx/event.h>
|
|
#include <wx/stdpaths.h>
|
|
|
|
#include <advanced_config.h>
|
|
#include <api/api_plugin_manager.h> // traceApi
|
|
#include <api/api_server.h>
|
|
#include <api/api_handler_common.h>
|
|
#include <kiid.h>
|
|
#include <kinng.h>
|
|
#include <paths.h>
|
|
#include <pgm_base.h>
|
|
#include <settings/common_settings.h>
|
|
#include <string_utils.h>
|
|
|
|
#include <api/common/envelope.pb.h>
|
|
|
|
using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode;
|
|
|
|
|
|
wxString KICAD_API_SERVER::s_logFileName = "api.log";
|
|
|
|
|
|
wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
|
|
|
|
|
|
KICAD_API_SERVER::KICAD_API_SERVER() :
|
|
wxEvtHandler(),
|
|
m_token( KIID().AsStdString() ),
|
|
m_readyToReply( false )
|
|
{
|
|
m_commonHandler = std::make_unique<API_HANDLER_COMMON>();
|
|
RegisterHandler( m_commonHandler.get() );
|
|
|
|
if( !Pgm().GetCommonSettings()->m_Api.enable_server )
|
|
{
|
|
wxLogTrace( traceApi, "Server: disabled by user preferences." );
|
|
return;
|
|
}
|
|
|
|
Start();
|
|
}
|
|
|
|
|
|
KICAD_API_SERVER::~KICAD_API_SERVER()
|
|
{
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::Start()
|
|
{
|
|
if( Running() )
|
|
return;
|
|
|
|
wxFileName socket;
|
|
#ifdef __WXMAC__
|
|
socket.AssignDir( wxS( "/tmp" ) );
|
|
#else
|
|
socket.AssignDir( wxStandardPaths::Get().GetTempDir() );
|
|
#endif
|
|
socket.AppendDir( wxS( "kicad" ) );
|
|
socket.SetFullName( wxS( "api.sock" ) );
|
|
|
|
if( !PATHS::EnsurePathExists( socket.GetPath() ) )
|
|
{
|
|
wxLogTrace( traceApi, wxString::Format( "Server: socket path %s could not be created",
|
|
socket.GetPath() ) );
|
|
return;
|
|
}
|
|
|
|
if( socket.FileExists() )
|
|
{
|
|
socket.SetFullName( wxString::Format( wxS( "api-%ul.sock" ), ::wxGetProcessId() ) );
|
|
|
|
if( socket.FileExists() )
|
|
{
|
|
wxLogTrace( traceApi, wxString::Format( "Server: PID socket path %s already exists!",
|
|
socket.GetFullPath() ) );
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_server = std::make_unique<KINNG_REQUEST_SERVER>(
|
|
fmt::format( "ipc://{}", socket.GetFullPath().ToStdString() ) );
|
|
m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } );
|
|
|
|
m_logFilePath.AssignDir( PATHS::GetLogsPath() );
|
|
m_logFilePath.SetName( s_logFileName );
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
{
|
|
PATHS::EnsurePathExists( PATHS::GetLogsPath() );
|
|
log( fmt::format( "--- KiCad API server started at {} ---\n", SocketPath() ) );
|
|
}
|
|
|
|
wxLogTrace( traceApi, wxString::Format( "Server: listening at %s", SocketPath() ) );
|
|
|
|
Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::Stop()
|
|
{
|
|
if( !Running() )
|
|
return;
|
|
|
|
wxLogTrace( traceApi, "Stopping server" );
|
|
Unbind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
|
|
|
|
m_server->Stop();
|
|
m_server.reset( nullptr );
|
|
}
|
|
|
|
|
|
bool KICAD_API_SERVER::Running() const
|
|
{
|
|
return m_server && m_server->Running();
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::RegisterHandler( API_HANDLER* aHandler )
|
|
{
|
|
wxCHECK( aHandler, /* void */ );
|
|
m_handlers.insert( aHandler );
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::DeregisterHandler( API_HANDLER* aHandler )
|
|
{
|
|
m_handlers.erase( aHandler );
|
|
}
|
|
|
|
|
|
std::string KICAD_API_SERVER::SocketPath() const
|
|
{
|
|
return m_server ? m_server->SocketPath() : "";
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
|
|
{
|
|
if( !m_readyToReply )
|
|
{
|
|
ApiResponse notHandled;
|
|
notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY );
|
|
notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" );
|
|
m_server->Reply( notHandled.SerializeAsString() );
|
|
log( "Got incoming request but was not yet ready to reply." );
|
|
return;
|
|
}
|
|
|
|
wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT );
|
|
|
|
// We don't actually need write access to this string, but client data is non-const
|
|
evt->SetClientData( static_cast<void*>( aRequest ) );
|
|
|
|
// Takes ownership and frees the wxCommandEvent
|
|
QueueEvent( evt );
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
|
|
{
|
|
std::string& requestString = *static_cast<std::string*>( aEvent.GetClientData() );
|
|
ApiRequest request;
|
|
|
|
if( !request.ParseFromString( requestString ) )
|
|
{
|
|
ApiResponse error;
|
|
error.mutable_header()->set_kicad_token( m_token );
|
|
error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
error.mutable_status()->set_error_message( "request could not be parsed" );
|
|
m_server->Reply( error.SerializeAsString() );
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
log( "Response (ERROR): " + error.Utf8DebugString() );
|
|
}
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
log( "Request: " + request.Utf8DebugString() );
|
|
|
|
if( !request.header().kicad_token().empty() &&
|
|
request.header().kicad_token().compare( m_token ) != 0 )
|
|
{
|
|
ApiResponse error;
|
|
error.mutable_header()->set_kicad_token( m_token );
|
|
error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH );
|
|
error.mutable_status()->set_error_message(
|
|
"the provided kicad_token did not match this KiCad instance's token" );
|
|
m_server->Reply( error.SerializeAsString() );
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
log( "Response (ERROR): " + error.Utf8DebugString() );
|
|
}
|
|
|
|
API_RESULT result;
|
|
|
|
for( API_HANDLER* handler : m_handlers )
|
|
{
|
|
result = handler->Handle( request );
|
|
|
|
if( result.has_value() )
|
|
break;
|
|
else if( result.error().status() != ApiStatusCode::AS_UNHANDLED )
|
|
break;
|
|
}
|
|
|
|
// Note: at the point we call Reply(), we no longer own requestString.
|
|
|
|
if( result.has_value() )
|
|
{
|
|
result->mutable_header()->set_kicad_token( m_token );
|
|
m_server->Reply( result->SerializeAsString() );
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
log( "Response: " + result->Utf8DebugString() );
|
|
}
|
|
else
|
|
{
|
|
ApiResponse error;
|
|
error.mutable_status()->CopyFrom( result.error() );
|
|
error.mutable_header()->set_kicad_token( m_token );
|
|
|
|
if( result.error().status() == ApiStatusCode::AS_UNHANDLED )
|
|
{
|
|
std::string type = "<unparseable Any>";
|
|
google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type );
|
|
std::string msg = fmt::format( "no handler available for request of type {}", type );
|
|
error.mutable_status()->set_error_message( msg );
|
|
}
|
|
|
|
m_server->Reply( error.SerializeAsString() );
|
|
|
|
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
|
|
log( "Response (ERROR): " + error.Utf8DebugString() );
|
|
}
|
|
}
|
|
|
|
|
|
void KICAD_API_SERVER::log( const std::string& aOutput )
|
|
{
|
|
FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) );
|
|
|
|
if( !fp )
|
|
return;
|
|
|
|
wxString out;
|
|
wxDateTime now = wxDateTime::Now();
|
|
|
|
fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ),
|
|
now.FormatISOCombined(), aOutput ) ) );
|
|
fclose( fp );
|
|
}
|