KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_remote_symbol.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "panel_remote_symbol.h"
25
26#include <bitmaps.h>
27#include <build_version.h>
28#include <common.h>
29#include <ki_exception.h>
30#include <pgm_base.h>
31#include <project.h>
32#include <project_sch.h>
33#include <sch_edit_frame.h>
34#include <sch_symbol.h>
36#include <eeschema_settings.h>
41#include <lib_symbol.h>
42#include <sch_io/sch_io_mgr.h>
43#include <tool/tool_manager.h>
44#include <tools/sch_actions.h>
47#include <nlohmann/json.hpp>
48#include <kiplatform/ui.h>
49
50#ifndef wxUSE_BASE64
51#define wxUSE_BASE64 1
52#endif
53#include <wx/base64.h>
54
55#include <wx/datetime.h>
56#include <wx/choice.h>
57#include <wx/dir.h>
58#include <wx/ffile.h>
59#include <wx/filefn.h>
60#include <wx/intl.h>
61#include <wx/log.h>
62#include <wx/mstream.h>
63#include <wx/settings.h>
64#include <wx/sizer.h>
65#include <wx/strconv.h>
66#include <wx/webview.h>
67#include <zstd.h>
68
69wxString PANEL_REMOTE_SYMBOL::jsonString( const nlohmann::json& aObject, const char* aKey ) const
70{
71 auto it = aObject.find( aKey );
72
73 if( it != aObject.end() && it->is_string() )
74 return wxString::FromUTF8( it->get<std::string>() );
75
76 return wxString();
77}
78
79bool PANEL_REMOTE_SYMBOL::decodeBase64Payload( const std::string& aEncoded,
80 std::vector<uint8_t>& aOutput,
81 wxString& aError ) const
82{
83 if( aEncoded.empty() )
84 {
85 aError = _( "Missing payload data." );
86 return false;
87 }
88
89 wxMemoryBuffer buffer = wxBase64Decode( wxString::FromUTF8( aEncoded.c_str() ) );
90
91 if( buffer.IsEmpty() )
92 {
93 aError = _( "Failed to decode base64 payload." );
94 return false;
95 }
96
97 aOutput.resize( buffer.GetDataLen() );
98 memcpy( aOutput.data(), buffer.GetData(), buffer.GetDataLen() );
99 return true;
100}
101
102bool PANEL_REMOTE_SYMBOL::decompressIfNeeded( const std::string& aCompression,
103 const std::vector<uint8_t>& aInput,
104 std::vector<uint8_t>& aOutput,
105 wxString& aError ) const
106{
107 if( aCompression.empty() || aCompression == "NONE" )
108 {
109 aOutput = aInput;
110 return true;
111 }
112
113 if( aCompression != "ZSTD" )
114 {
115 aError = wxString::Format( _( "Unsupported compression '%s'." ),
116 wxString::FromUTF8( aCompression ) );
117 return false;
118 }
119
120 if( aInput.empty() )
121 {
122 aError = _( "Compressed payload was empty." );
123 return false;
124 }
125
126 unsigned long long expectedSize =
127 ZSTD_getFrameContentSize( aInput.data(), aInput.size() );
128
129 if( expectedSize == ZSTD_CONTENTSIZE_ERROR || expectedSize == ZSTD_CONTENTSIZE_UNKNOWN )
130 expectedSize = static_cast<unsigned long long>( aInput.size() ) * 4;
131
132 aOutput.resize( expectedSize );
133
134 size_t decompressed = ZSTD_decompress( aOutput.data(), expectedSize,
135 aInput.data(), aInput.size() );
136
137 if( ZSTD_isError( decompressed ) )
138 {
139 aError = wxString::Format( _( "ZSTD decompression failed: %s" ),
140 wxString::FromUTF8( ZSTD_getErrorName( decompressed ) ) );
141 return false;
142 }
143
144 aOutput.resize( decompressed );
145 return true;
146}
147
148wxString PANEL_REMOTE_SYMBOL::sanitizeForScript( const std::string& aJson ) const
149{
150 wxString script = wxString::FromUTF8( aJson.c_str() );
151 script.Replace( "\\", "\\\\" );
152 script.Replace( "'", "\\'" );
153 return script;
154}
155
156wxString PANEL_REMOTE_SYMBOL::sanitizeFileComponent( const wxString& aValue,
157 const wxString& aDefault ) const
158{
159 wxString result = aValue;
160 result.Trim( true ).Trim( false );
161
162 if( result.IsEmpty() )
163 result = aDefault;
164
165 for( size_t i = 0; i < result.length(); ++i )
166 {
167 wxUniChar ch = result[i];
168
169 if( ch == '/' || ch == '\\' || ch == ':' )
170 result[i] = '_';
171 }
172
173 return result;
174}
175
176bool PANEL_REMOTE_SYMBOL::writeBinaryFile( const wxFileName& aOutput,
177 const std::vector<uint8_t>& aPayload,
178 wxString& aError ) const
179{
180 if( aPayload.empty() )
181 {
182 aError = _( "Payload was empty." );
183 return false;
184 }
185
186 wxFileName targetDir = aOutput;
187 targetDir.SetFullName( wxEmptyString );
188
189 if( !targetDir.DirExists() )
190 {
191 if( !targetDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
192 {
193 aError = wxString::Format( _( "Unable to create '%s'." ), targetDir.GetFullPath() );
194 return false;
195 }
196 }
197
198 wxFFile file( aOutput.GetFullPath(), wxS( "wb" ) );
199
200 if( !file.IsOpened() )
201 {
202 aError = wxString::Format( _( "Unable to open '%s' for writing." ), aOutput.GetFullPath() );
203 return false;
204 }
205
206 if( file.Write( aPayload.data(), aPayload.size() ) != aPayload.size() )
207 {
208 aError = wxString::Format( _( "Failed to write '%s'." ), aOutput.GetFullPath() );
209 return false;
210 }
211
212 file.Close();
213 return true;
214}
215
216
218 const std::vector<uint8_t>& aPayload, const wxString& aLibItemName,
219 wxString& aError ) const
220{
221 if( aPayload.empty() )
222 {
223 aError = _( "Symbol payload was empty." );
224 return nullptr;
225 }
226
227 wxString tempPath = wxFileName::CreateTempFileName( wxS( "remote_symbol" ) );
228
229 if( tempPath.IsEmpty() )
230 {
231 aError = _( "Unable to create a temporary file for the symbol payload." );
232 return nullptr;
233 }
234
235 wxFileName tempFile( tempPath );
236
237 wxFFile file( tempFile.GetFullPath(), wxS( "wb" ) );
238
239 if( !file.IsOpened() )
240 {
241 aError = _( "Unable to create a temporary file for the symbol payload." );
242 wxRemoveFile( tempFile.GetFullPath() );
243 return nullptr;
244 }
245
246 if( file.Write( aPayload.data(), aPayload.size() ) != aPayload.size() )
247 {
248 aError = _( "Failed to write the temporary symbol payload." );
249 file.Close();
250 wxRemoveFile( tempFile.GetFullPath() );
251 return nullptr;
252 }
253
254 file.Close();
255
256 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
257
258 if( !plugin )
259 {
260 aError = _( "Unable to access the KiCad symbol plugin." );
261 wxRemoveFile( tempFile.GetFullPath() );
262 return nullptr;
263 }
264
265 std::unique_ptr<LIB_SYMBOL> symbol;
266
267 try
268 {
269 LIB_SYMBOL* loaded = plugin->LoadSymbol( tempFile.GetFullPath(), aLibItemName );
270
271 if( loaded )
272 {
273 symbol.reset( loaded );
274 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
275 "loadSymbolFromPayload: loaded symbol %s from temporary file %s",
276 aLibItemName.ToUTF8().data(), tempFile.GetFullPath().ToUTF8().data() );
277 }
278 else
279 {
280 aError = _( "Symbol payload did not include the expected symbol." );
281 }
282 }
283 catch( const IO_ERROR& e )
284 {
285 aError = wxString::Format( _( "Unable to decode the symbol payload: %s" ), e.What() );
286 }
287
288 wxRemoveFile( tempFile.GetFullPath() );
289 return symbol;
290}
291
292
294 wxPanel( aParent ),
295 m_frame( aParent ),
296 m_dataSourceChoice( nullptr ),
297 m_configButton( nullptr ),
298 m_webView( nullptr ),
299 m_pcm( std::make_shared<PLUGIN_CONTENT_MANAGER>( []( int ) {} ) ),
300 m_sessionId( 0 ),
301 m_messageIdCounter( 0 ),
302 m_pendingHandshake( false )
303{
304 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
305
306 wxBoxSizer* controlsSizer = new wxBoxSizer( wxHORIZONTAL );
307 m_dataSourceChoice = new wxChoice( this, wxID_ANY );
308 m_dataSourceChoice->SetMinSize( FromDIP( wxSize( 160, -1 ) ) );
309 m_dataSourceChoice->SetToolTip( _( "Select which remote data source to query." ) );
310 controlsSizer->Add( m_dataSourceChoice, 1, wxEXPAND | wxRIGHT, FromDIP( 2 ) );
311
312 m_configButton = new BITMAP_BUTTON( this, wxID_ANY );
313 m_configButton->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
314 m_configButton->SetPadding( FromDIP( 2 ) );
315 m_configButton->SetToolTip( _( "Configure remote data sources." ) );
316 controlsSizer->Add( m_configButton, 0, wxALIGN_CENTER_VERTICAL );
317
318 topSizer->Add( controlsSizer, 0, wxEXPAND | wxALL, FromDIP( 4 ) );
319
320 m_webView = new WEBVIEW_PANEL( this );
321 m_webView->AddMessageHandler( wxS( "kicad" ),
322 [this]( const wxString& payload )
323 {
324 onKicadMessage( payload );
325 } );
326 m_webView->SetHandleExternalLinks( true );
327
328 if( wxWebView* browser = m_webView->GetWebView() )
329 {
330 browser->Bind( wxEVT_WEBVIEW_LOADED, &PANEL_REMOTE_SYMBOL::onWebViewLoaded, this );
331 }
332
333 topSizer->Add( m_webView, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP( 2 ) );
334
335 SetSizer( topSizer );
336
337 m_dataSourceChoice->Bind( wxEVT_CHOICE, &PANEL_REMOTE_SYMBOL::onDataSourceChanged, this );
338 m_configButton->Bind( wxEVT_BUTTON, &PANEL_REMOTE_SYMBOL::onConfigure, this );
339
340 RefreshDataSources();
341
342 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "PANEL_REMOTE_SYMBOL constructed (frame=%p)", (void*)aParent );
343}
344
345
347{
348 if( wxWebView* browser = m_webView->GetWebView() )
349 {
350 browser->Bind( wxEVT_WEBVIEW_LOADED, &PANEL_REMOTE_SYMBOL::onWebViewLoaded, this );
351 }
352}
353
354
356{
357 if( KICAD_SETTINGS* cfg = GetAppSettings<KICAD_SETTINGS>( "kicad" ) )
358 m_pcm->SetRepositoryList( cfg->m_PcmRepositories );
359
360 m_dataSources.clear();
361 m_dataSourceChoice->Clear();
362
363 const std::vector<PCM_INSTALLATION_ENTRY> installed = m_pcm->GetInstalledPackages();
364
365 for( const PCM_INSTALLATION_ENTRY& entry : installed )
366 {
367 if( entry.package.type != PT_DATASOURCE )
368 continue;
369
370 wxString label = entry.package.name;
371
372 if( !entry.current_version.IsEmpty() )
373 label << wxS( " (" ) << entry.current_version << wxS( ")" );
374
375 m_dataSources.push_back( entry );
376 m_dataSourceChoice->Append( label );
377 }
378
379 if( m_dataSources.empty() )
380 {
381 m_dataSourceChoice->Enable( false );
382 showMessage( _( "No remote data sources are currently installed." ) );
383 return;
384 }
385
386 m_dataSourceChoice->Enable( true );
387 m_dataSourceChoice->SetSelection( 0 );
388 loadDataSource( 0 );
389}
390
391
392void PANEL_REMOTE_SYMBOL::onDataSourceChanged( wxCommandEvent& aEvent )
393{
394 const int selection = aEvent.GetSelection();
395
396 if( selection == wxNOT_FOUND )
397 return;
398
399 loadDataSource( static_cast<size_t>( selection ) );
400}
401
402
403void PANEL_REMOTE_SYMBOL::onConfigure( wxCommandEvent& aEvent )
404{
405 DIALOG_REMOTE_SYMBOL_CONFIG dlg( this );
406
407 dlg.ShowModal();
408
410}
411
412
414{
415 if( aIndex >= m_dataSources.size() )
416 return false;
417
418 return loadDataSource( m_dataSources[aIndex] );
419}
420
421
423{
424 std::optional<wxFileName> jsonPath = findDataSourceJson( aEntry );
425
426 if( !jsonPath )
427 {
428 wxLogWarning( "No JSON configuration found for data source %s", aEntry.package.identifier );
429 showMessage( wxString::Format( _( "No configuration JSON found for '%s'." ),
430 aEntry.package.name ) );
431 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: no json for %s", aEntry.package.identifier );
432 return false;
433 }
434
435 wxFFile file( jsonPath->GetFullPath(), "rb" );
436
437 if( !file.IsOpened() )
438 {
439 wxLogWarning( "Unable to open remote data source JSON: %s", jsonPath->GetFullPath() );
440 showMessage( wxString::Format( _( "Unable to open '%s'." ), jsonPath->GetFullPath() ) );
441 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: cannot open %s", jsonPath->GetFullPath() );
442 return false;
443 }
444
445 wxString jsonContent;
446
447 if( !file.ReadAll( &jsonContent, wxConvUTF8 ) )
448 {
449 wxLogWarning( "Failed to read remote data source JSON: %s", jsonPath->GetFullPath() );
450 showMessage( wxString::Format( _( "Unable to read '%s'." ), jsonPath->GetFullPath() ) );
451 return false;
452 }
453
454 if( std::optional<wxString> url = extractUrlFromJson( jsonContent ) )
455 {
456 m_pendingHandshake = true;
457 m_webView->LoadURL( *url );
458 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: loading URL %s", (*url).ToUTF8().data() );
459 return true;
460 }
461
462 wxLogWarning( "Remote data source JSON did not produce a valid URL for %s",
463 aEntry.package.identifier );
464 showMessage( wxString::Format( _( "Unable to load remote data for '%s'." ),
465 aEntry.package.name ) );
466 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: failed to find URL in %s", jsonPath->GetFullPath() );
467 return false;
468}
469
470
471std::optional<wxFileName>
473{
474 wxString cleanId = aEntry.package.identifier;
475 cleanId.Replace( '.', '_' );
476
477 wxFileName baseDir = wxFileName::DirName( m_pcm->Get3rdPartyPath() );
478 baseDir.AppendDir( wxS( "resources" ) );
479 baseDir.AppendDir( cleanId );
480
481 const wxString resourcesPath = baseDir.GetFullPath();
482
483 if( !wxDirExists( resourcesPath ) )
484 return std::nullopt;
485
486 const std::vector<wxString> preferredNames = {
487 wxS( "remote_symbol.json" ),
488 wxS( "datasource.json" )
489 };
490
491 for( const wxString& candidate : preferredNames )
492 {
493 wxFileName file( baseDir );
494 file.SetFullName( candidate );
495
496 if( file.FileExists() )
497 return file;
498 }
499
500 wxDir dir( resourcesPath );
501
502 if( !dir.IsOpened() )
503 return std::nullopt;
504
505 wxString jsonFile;
506
507 if( dir.GetFirst( &jsonFile, wxS( "*.json" ), wxDIR_FILES ) )
508 {
509 wxFileName fallback( baseDir );
510 fallback.SetFullName( jsonFile );
511 return fallback;
512 }
513
514 return std::nullopt;
515}
516
517
518void PANEL_REMOTE_SYMBOL::showMessage( const wxString& aMessage )
519{
520 if( !m_webView )
521 return;
522
523 wxString escaped = aMessage;
524 escaped.Replace( "&", "&amp;" );
525 escaped.Replace( "<", "&lt;" );
526 escaped.Replace( ">", "&gt;" );
527
528 wxColour bgColour = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
529 wxColour fgColour = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
530
531 wxString html;
532 html << wxS( "<html><head><style>" )
533 << wxString::Format( wxS( "body { background-color: #%02x%02x%02x; color: #%02x%02x%02x; "
534 "font-family: system-ui, sans-serif; padding: 10px; }" ),
535 bgColour.Red(), bgColour.Green(), bgColour.Blue(),
536 fgColour.Red(), fgColour.Green(), fgColour.Blue() )
537 << wxS( "</style></head><body><p>" ) << escaped << wxS( "</p></body></html>" );
538
539 m_webView->SetPage( html );
540}
541
542
543std::optional<wxString> PANEL_REMOTE_SYMBOL::extractUrlFromJson( const wxString& aJsonContent ) const
544{
545 if( aJsonContent.IsEmpty() )
546 return std::nullopt;
547
548 wxScopedCharBuffer utf8 = aJsonContent.ToUTF8();
549
550 if( !utf8 || utf8.length() == 0 )
551 return std::nullopt;
552
553 try
554 {
555 nlohmann::json parsed = nlohmann::json::parse( utf8.data() );
556 std::string url;
557
558 if( parsed.is_string() )
559 {
560 url = parsed.get<std::string>();
561 }
562 else if( parsed.is_object() )
563 {
564 auto extractString = [&]( const char* key ) -> std::string
565 {
566 auto it = parsed.find( key );
567
568 if( it != parsed.end() && it->is_string() )
569 return it->get<std::string>();
570
571 return {};
572 };
573
574 auto extractInt = [&]( const char* key ) -> std::optional<int>
575 {
576 auto it = parsed.find( key );
577
578 if( it == parsed.end() )
579 return std::nullopt;
580
581 if( it->is_number_integer() )
582 return it->get<int>();
583
584 if( it->is_string() )
585 {
586 try
587 {
588 return std::stoi( it->get<std::string>() );
589 }
590 catch( ... )
591 {
592 }
593 }
594
595 return std::nullopt;
596 };
597
598 std::string host = extractString( "host" );
599 std::optional<int> port = extractInt( "port" );
600 std::string path = extractString( "path" );
601
602 if( !host.empty() )
603 {
604 if( path.empty() )
605 path = "/";
606
607 if( port && *port > 0 )
608 url = wxString::Format( "%s:%d%s", host, *port, path ).ToStdString();
609 else
610 url = host + path;
611 }
612
613 if( url.empty() )
614 url = extractString( "url" );
615
616 if( url.empty() )
617 {
618 for( const char* key : { "website", "endpoint" } )
619 {
620 url = extractString( key );
621
622 if( !url.empty() )
623 break;
624 }
625 }
626
627 if( url.empty() )
628 {
629 for( const auto& [name, value] : parsed.items() )
630 {
631 if( value.is_string() )
632 {
633 const std::string candidate = value.get<std::string>();
634
635 if( candidate.rfind( "http", 0 ) == 0 || candidate.rfind( "file", 0 ) == 0 )
636 {
637 url = candidate;
638 break;
639 }
640 }
641 }
642 }
643 }
644
645 if( url.empty() )
646 return std::nullopt;
647
648 return wxString::FromUTF8( url.c_str() );
649 }
650 catch( const std::exception& e )
651 {
652 wxLogWarning( "Failed to parse remote symbol JSON: %s", e.what() );
653 return std::nullopt;
654 }
655}
656
657
658void PANEL_REMOTE_SYMBOL::onKicadMessage( const wxString& aMessage )
659{
660 wxScopedCharBuffer utf8 = aMessage.ToUTF8();
661
662 if( !utf8 || utf8.length() == 0 )
663 {
664 wxLogWarning( "Remote symbol RPC: empty payload." );
665 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: empty payload" );
666 return;
667 }
668
669 try
670 {
671 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: received payload size=%d", (int)utf8.length() );
672 handleRpcMessage( nlohmann::json::parse( utf8.data() ) );
673 }
674 catch( const std::exception& e )
675 {
676 wxLogWarning( "Remote symbol RPC parse error: %s", e.what() );
677 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: parse error %s", e.what() );
678 }
679}
680
681
682void PANEL_REMOTE_SYMBOL::onWebViewLoaded( wxWebViewEvent& aEvent )
683{
684 wxUnusedVar( aEvent );
685
687 {
688 CallAfter( [this]()
689 {
691 {
692 m_pendingHandshake = false;
694 }
695 } );
696 }
697
698 aEvent.Skip();
699}
700
701
703{
704 if( !m_webView )
705 return;
706
707 m_sessionId = KIID();
709
710 nlohmann::json params = nlohmann::json::object();
711 params["client_name"] = "KiCad";
712 params["client_version"] = GetSemanticVersion().ToStdString();
713 params["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
714
715 sendRpcMessage( wxS( "NEW_SESSION" ), std::move( params ) );
716 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "beginSessionHandshake: NEW_SESSION sent, session=%s", m_sessionId.AsString() );
717}
718
719
720void PANEL_REMOTE_SYMBOL::sendRpcMessage( const wxString& aCommand,
721 nlohmann::json aParameters,
722 std::optional<int> aResponseTo,
723 const wxString& aStatus,
724 const std::string& aData,
725 const wxString& aErrorCode,
726 const wxString& aErrorMessage )
727{
728 if( !m_webView )
729 return;
730
731 nlohmann::json payload = nlohmann::json::object();
732 payload["version"] = REMOTE_SYMBOL_SESSION_VERSION;
733 payload["session_id"] = m_sessionId.AsStdString();
734 payload["message_id"] = ++m_messageIdCounter;
735 payload["command"] = aCommand.ToStdString();
736
737 if( aResponseTo )
738 payload["response_to"] = *aResponseTo;
739
740 if( !aStatus.IsEmpty() )
741 payload["status"] = aStatus.ToStdString();
742
743 if( !aParameters.is_null() && !aParameters.empty() )
744 payload["parameters"] = std::move( aParameters );
745
746 if( !aData.empty() )
747 payload["data"] = aData;
748
749 if( !aErrorCode.IsEmpty() )
750 payload["error_code"] = aErrorCode.ToStdString();
751
752 if( !aErrorMessage.IsEmpty() )
753 payload["error_message"] = aErrorMessage.ToStdString();
754
755 wxString script = wxString::Format( wxS( "window.kiclient.postMessage('%s');" ),
756 sanitizeForScript( payload.dump() ) );
757 m_webView->RunScriptAsync( script );
758 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "sendRpcMessage: command=%s message_id=%d status=%s",
759 aCommand.ToUTF8().data(), payload["message_id"].get<int>(), aStatus.ToUTF8().data() );
760}
761
762
763void PANEL_REMOTE_SYMBOL::respondWithError( const wxString& aCommand, int aResponseTo,
764 const wxString& aErrorCode,
765 const wxString& aErrorMessage )
766{
767 sendRpcMessage( aCommand, nlohmann::json::object(), aResponseTo, wxS( "ERROR" ),
768 std::string(), aErrorCode, aErrorMessage );
769}
770
771
772void PANEL_REMOTE_SYMBOL::handleRpcMessage( const nlohmann::json& aMessage )
773{
774 if( !aMessage.is_object() )
775 return;
776
777 const wxString command = jsonString( aMessage, "command" );
778
779 if( command.IsEmpty() )
780 return;
781
782 auto messageIdIt = aMessage.find( "message_id" );
783
784 if( messageIdIt == aMessage.end() || !messageIdIt->is_number_integer() )
785 return;
786
787 const int messageId = messageIdIt->get<int>();
788
789 const int version = aMessage.value( "version", 0 );
790
791 if( version != REMOTE_SYMBOL_SESSION_VERSION )
792 {
793 respondWithError( command, messageId, wxS( "UNSUPPORTED_VERSION" ),
794 wxString::Format( _( "Unsupported RPC version %d." ), version ) );
795 return;
796 }
797
798 const wxString sessionId = jsonString( aMessage, "session_id" );
799
800 if( sessionId.IsEmpty() )
801 {
802 respondWithError( command, messageId, wxS( "INVALID_PARAMETERS" ),
803 _( "Missing session identifier." ) );
804 return;
805 }
806
807 if( !sessionId.IsSameAs( m_sessionId.AsString() ) )
808 wxLogWarning( "Remote symbol RPC session mismatch (expected %s, got %s).",
809 m_sessionId.AsString(), sessionId );
810
811 const wxString status = jsonString( aMessage, "status" );
812
813 if( status.IsSameAs( wxS( "ERROR" ), false ) )
814 {
815 wxLogWarning( "Remote symbol RPC error (%s): %s", jsonString( aMessage, "error_code" ),
816 jsonString( aMessage, "error_message" ) );
817 return;
818 }
819
820 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "handleRpcMessage: command=%s message_id=%d status=%s session=%s",
821 command.ToUTF8().data(), messageId, status.ToUTF8().data(), sessionId.ToUTF8().data() );
822
823 nlohmann::json params = nlohmann::json::object();
824 auto paramsIt = aMessage.find( "parameters" );
825
826 if( paramsIt != aMessage.end() && paramsIt->is_object() )
827 params = *paramsIt;
828
829 const std::string data = aMessage.value( "data", std::string() );
830
831 if( command == wxS( "NEW_SESSION" ) )
832 {
833 nlohmann::json reply = nlohmann::json::object();
834 reply["client_name"] = "KiCad";
835 reply["client_version"] = GetSemanticVersion().ToStdString();
836 reply["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
837 sendRpcMessage( command, std::move( reply ), messageId );
838 return;
839 }
840 else if( command == wxS( "GET_KICAD_VERSION" ) )
841 {
842 nlohmann::json reply = nlohmann::json::object();
843 reply["kicad_version"] = GetSemanticVersion().ToStdString();
844 sendRpcMessage( command, std::move( reply ), messageId );
845 return;
846 }
847 else if( command == wxS( "LIST_SUPPORTED_VERSIONS" ) )
848 {
849 nlohmann::json reply = nlohmann::json::object();
850 reply["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
851 sendRpcMessage( command, std::move( reply ), messageId );
852 return;
853 }
854 else if( command == wxS( "CAPABILITIES" ) )
855 {
856 nlohmann::json reply = nlohmann::json::object();
857 reply["commands"] = { "NEW_SESSION", "GET_KICAD_VERSION", "LIST_SUPPORTED_VERSIONS",
858 "CAPABILITIES", "PING", "PONG", "DL_SYMBOL", "DL_FOOTPRINT",
859 "DL_SPICE", "DL_3DMODEL" };
860 reply["compression"] = { "NONE", "ZSTD" };
861 reply["max_message_size"] = 0;
862 sendRpcMessage( command, std::move( reply ), messageId );
863 return;
864 }
865 else if( command == wxS( "PING" ) )
866 {
867 nlohmann::json reply = nlohmann::json::object();
868
869 if( params.contains( "nonce" ) )
870 reply["nonce"] = params["nonce"];
871
872 sendRpcMessage( wxS( "PONG" ), std::move( reply ), messageId );
873 return;
874 }
875 else if( command == wxS( "PONG" ) )
876 {
877 return;
878 }
879
880 const wxString compression = jsonString( params, "compression" );
881
882 if( command.StartsWith( wxS( "DL_" ) ) )
883 {
884 if( compression.IsEmpty() )
885 {
886 respondWithError( command, messageId, wxS( "INVALID_PARAMETERS" ),
887 _( "Missing compression metadata." ) );
888 return;
889 }
890
891 std::vector<uint8_t> decoded;
892 wxString error;
893
894 if( !decodeBase64Payload( data, decoded, error ) )
895 {
896 respondWithError( command, messageId, wxS( "INVALID_PAYLOAD" ), error );
897 return;
898 }
899
900 std::vector<uint8_t> payload;
901 wxScopedCharBuffer compUtf8 = compression.ToUTF8();
902 std::string compressionStr = compUtf8 ? std::string( compUtf8.data() ) : std::string();
903
904 if( !decompressIfNeeded( compressionStr, decoded, payload, error ) )
905 {
906 respondWithError( command, messageId, wxS( "INVALID_PAYLOAD" ), error );
907 return;
908 }
909
910 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "handleRpcMessage: decoded size=%zu decompressed size=%zu command=%s",
911 decoded.size(), payload.size(), command.ToUTF8().data() );
912
913 bool ok = false;
914
915 if( command == wxS( "DL_SYMBOL" ) )
916 ok = receiveSymbol( params, payload, error );
917 else if( command == wxS( "DL_FOOTPRINT" ) )
918 ok = receiveFootprint( params, payload, error );
919 else if( command == wxS( "DL_3DMODEL" ) )
920 ok = receive3DModel( params, payload, error );
921 else if( command == wxS( "DL_SPICE" ) )
922 ok = receiveSPICEModel( params, payload, error );
923 else
924 {
925 respondWithError( command, messageId, wxS( "UNKNOWN_COMMAND" ),
926 wxString::Format( _( "Command '%s' is not supported." ), command ) );
927 return;
928 }
929
930 if( ok )
931 sendRpcMessage( command, nlohmann::json::object(), messageId );
932 else
933 respondWithError( command, messageId, wxS( "INTERNAL_ERROR" ),
934 error.IsEmpty() ? _( "Unable to store payload." ) : error );
935
936 return;
937 }
938
939 respondWithError( command, messageId, wxS( "UNKNOWN_COMMAND" ),
940 wxString::Format( _( "Command '%s' is not supported." ), command ) );
941}
942
943
944bool PANEL_REMOTE_SYMBOL::ensureDestinationRoot( wxFileName& aOutDir, wxString& aError ) const
945{
947
948 if( !settings )
949 {
950 aError = _( "Unable to load schematic settings." );
951 return false;
952 }
953
954 wxString destination = settings->m_RemoteSymbol.destination_dir;
955
956 if( destination.IsEmpty() )
958
959 destination = ExpandEnvVarSubstitutions( destination,
960 &Pgm().GetSettingsManager().Prj() );
961 destination.Trim( true ).Trim( false );
962
963 if( destination.IsEmpty() )
964 {
965 aError = _( "Destination directory is not configured." );
966 return false;
967 }
968
969 wxFileName dir = wxFileName::DirName( destination );
970 dir.Normalize( FN_NORMALIZE_FLAGS );
971
972 if( !dir.DirExists() )
973 {
974 if( !dir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
975 {
976 aError = wxString::Format( _( "Unable to create directory '%s'." ), dir.GetFullPath() );
977 return false;
978 }
979 }
980
981 aOutDir = dir;
982 return true;
983}
984
985
986bool PANEL_REMOTE_SYMBOL::ensureSymbolLibraryEntry( const wxFileName& aLibraryFile,
987 const wxString& aNickname, bool aGlobalTable,
988 wxString& aError ) const
989{
991 std::optional<LIBRARY_TABLE*> tableOpt = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL,
993
994 if( !tableOpt )
995 {
996 aError = _( "Unable to access the symbol library table." );
997 return false;
998 }
999
1000 LIBRARY_TABLE* table = *tableOpt;
1001 const wxString fullPath = aLibraryFile.GetFullPath();
1002
1003 if( table->HasRow( aNickname ) )
1004 {
1005 if( std::optional<LIBRARY_TABLE_ROW*> rowOpt = table->Row( aNickname ); rowOpt )
1006 {
1007 LIBRARY_TABLE_ROW* row = *rowOpt;
1008
1009 if( row->URI() != fullPath )
1010 {
1011 row->SetURI( fullPath );
1012
1013 if( !table->Save() )
1014 {
1015 aError = _( "Failed to update the symbol library table." );
1016 return false;
1017 }
1018 }
1019 }
1020
1021 return true;
1022 }
1023
1024 LIBRARY_TABLE_ROW& row = table->InsertRow();
1025 row.SetNickname( aNickname );
1026 row.SetURI( fullPath );
1027 row.SetType( wxS( "KiCad" ) );
1028 row.SetOptions( wxString() );
1029 row.SetDescription( _( "Remote download" ) );
1030 row.SetOk( true );
1031
1032 if( !table->Save() )
1033 {
1034 aError = _( "Failed to save the symbol library table." );
1035 return false;
1036 }
1037
1038 return true;
1039}
1040
1041
1042bool PANEL_REMOTE_SYMBOL::ensureFootprintLibraryEntry( const wxFileName& aLibraryDir,
1043 const wxString& aNickname,
1044 bool aGlobalTable, wxString& aError ) const
1045{
1046 LIBRARY_MANAGER& manager = Pgm().GetLibraryManager();
1047 std::optional<LIBRARY_TABLE*> tableOpt = manager.Table( LIBRARY_TABLE_TYPE::FOOTPRINT,
1049
1050 if( !tableOpt )
1051 {
1052 aError = _( "Unable to access the footprint library table." );
1053 return false;
1054 }
1055
1056 LIBRARY_TABLE* table = *tableOpt;
1057 const wxString fullPath = aLibraryDir.GetFullPath();
1058
1059 if( table->HasRow( aNickname ) )
1060 {
1061 if( std::optional<LIBRARY_TABLE_ROW*> rowOpt = table->Row( aNickname ); rowOpt )
1062 {
1063 LIBRARY_TABLE_ROW* row = *rowOpt;
1064
1065 if( row->URI() != fullPath )
1066 {
1067 row->SetURI( fullPath );
1068
1069 if( !table->Save() )
1070 {
1071 aError = _( "Failed to update the footprint library table." );
1072 return false;
1073 }
1074 }
1075 }
1076
1077 return true;
1078 }
1079
1080 LIBRARY_TABLE_ROW& row = table->InsertRow();
1081 row.SetNickname( aNickname );
1082 row.SetURI( fullPath );
1083 row.SetType( wxS( "KiCad" ) );
1084 row.SetOptions( wxString() );
1085 row.SetDescription( _( "Remote download" ) );
1086 row.SetOk( true );
1087
1088 if( !table->Save() )
1089 {
1090 aError = _( "Failed to save the footprint library table." );
1091 return false;
1092 }
1093
1094 return true;
1095}
1096
1097
1099{
1100 wxString prefix;
1101
1102 if( EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
1103 prefix = settings->m_RemoteSymbol.library_prefix;
1104
1105 if( prefix.IsEmpty() )
1107
1108 prefix.Trim( true ).Trim( false );
1109
1110 if( prefix.IsEmpty() )
1111 prefix = wxS( "remote" );
1112
1113 for( size_t i = 0; i < prefix.length(); ++i )
1114 {
1115 wxUniChar ch = prefix[i];
1116
1117 if( !( wxIsalnum( ch ) || ch == '_' || ch == '-' ) )
1118 prefix[i] = '_';
1119 }
1120
1121 return prefix;
1122}
1123
1124
1125bool PANEL_REMOTE_SYMBOL::placeDownloadedSymbol( const wxString& aNickname,
1126 const wxString& aLibItemName,
1127 wxString& aError )
1128{
1129 if( !m_frame )
1130 {
1131 aError = _( "No schematic editor is available for placement." );
1132 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: no frame available" );
1133 return false;
1134 }
1135
1136 if( aNickname.IsEmpty() || aLibItemName.IsEmpty() )
1137 {
1138 aError = _( "Downloaded symbol metadata is incomplete." );
1139 return false;
1140 }
1141
1142 LIB_ID libId;
1143 libId.SetLibNickname( aNickname );
1144 libId.SetLibItemName( aLibItemName );
1145
1146 LIB_SYMBOL* libSymbol = m_frame->GetLibSymbol( libId );
1147
1148 if( !libSymbol )
1149 {
1150 aError = _( "Unable to load the downloaded symbol for placement." );
1151 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: failed to load libSymbol %s:%s",
1152 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1153 return false;
1154 }
1155
1156 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: loaded libSymbol %s:%s",
1157 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1158
1159 SCH_SYMBOL* symbol = new SCH_SYMBOL( *libSymbol, libId, &m_frame->GetCurrentSheet(), 1 );
1160 symbol->SetParent( m_frame->GetScreen() );
1161
1162 if( EESCHEMA_SETTINGS* cfg = m_frame->eeconfig(); cfg && cfg->m_AutoplaceFields.enable )
1163 {
1164 symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
1165 }
1166
1167 TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
1168
1169 if( !toolMgr )
1170 {
1171 delete symbol;
1172 aError = _( "Unable to access the schematic placement tools." );
1173 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: no tool manager available" );
1174 return false;
1175 }
1176
1177 m_frame->Raise();
1179 SCH_ACTIONS::PLACE_SYMBOL_PARAMS{ symbol, true } );
1180 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: posted placeSymbol action for %s:%s",
1181 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1182 return true;
1183}
1184
1185
1186bool PANEL_REMOTE_SYMBOL::receiveSymbol( const nlohmann::json& aParams,
1187 const std::vector<uint8_t>& aPayload,
1188 wxString& aError )
1189{
1190 const wxString mode = jsonString( aParams, "mode" );
1191 const bool placeAfterDownload = mode.IsSameAs( wxS( "PLACE" ), false );
1192
1193 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1194 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1195 {
1196 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1197 return false;
1198 }
1199
1200 const wxString contentType = jsonString( aParams, "content_type" );
1201
1202 if( !contentType.IsSameAs( wxS( "KICAD_SYMBOL_V1" ), false ) )
1203 {
1204 aError = _( "Unsupported symbol payload type." );
1205 return false;
1206 }
1207
1208 if( !m_frame )
1209 {
1210 aError = _( "No schematic editor is available to store symbols." );
1211 return false;
1212 }
1213
1214 EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
1215
1216 if( !settings )
1217 {
1218 aError = _( "Unable to load schematic settings." );
1219 return false;
1220 }
1221
1222 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
1223
1224 wxFileName baseDir;
1225
1226 if( !ensureDestinationRoot( baseDir, aError ) )
1227 return false;
1228
1229 wxFileName symDir = baseDir;
1230 symDir.AppendDir( wxS( "symbols" ) );
1231
1232 if( !symDir.DirExists() && !symDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1233 {
1234 aError = wxString::Format( _( "Unable to create '%s'." ), symDir.GetFullPath() );
1235 return false;
1236 }
1237
1238 // Get symbol's internal name from "name" parameter
1239 wxString libItemName = jsonString( aParams, "name" );
1240 if( libItemName.IsEmpty() )
1241 libItemName = wxS( "symbol" );
1242 libItemName.Trim( true ).Trim( false );
1243
1244 // Get library name from "library" parameter, fallback to "symbols"
1245 wxString libraryName = jsonString( aParams, "library" );
1246 if( libraryName.IsEmpty() )
1247 libraryName = wxS( "symbols" );
1248 libraryName.Trim( true ).Trim( false );
1249
1250 wxString sanitizedLibName = sanitizeFileComponent( libraryName, wxS( "symbols" ) );
1251 const wxString nickname = sanitizedPrefix() + wxS( "_" ) + sanitizedLibName;
1252
1253 wxFileName outFile( symDir );
1254 outFile.SetFullName( nickname + wxS( ".kicad_sym" ) );
1255
1256 if( !ensureSymbolLibraryEntry( outFile, nickname, addToGlobal, aError ) )
1257 return false;
1258
1260
1261 if( !adapter )
1262 {
1263 aError = _( "Unable to access the symbol library manager." );
1264 return false;
1265 }
1266
1267 std::unique_ptr<LIB_SYMBOL> downloadedSymbol = loadSymbolFromPayload( aPayload, libItemName, aError );
1268
1269 if( !downloadedSymbol )
1270 {
1271 if( aError.IsEmpty() )
1272 aError = _( "Unable to parse the downloaded symbol." );
1273
1274 return false;
1275 }
1276
1277 downloadedSymbol->SetName( libItemName );
1278
1279 LIB_ID savedId;
1280 savedId.SetLibNickname( nickname );
1281 savedId.SetLibItemName( libItemName );
1282 downloadedSymbol->SetLibId( savedId );
1283
1284 if( adapter->SaveSymbol( nickname, downloadedSymbol.get(), true )
1286 {
1287 aError = _( "Unable to save the downloaded symbol." );
1288 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
1289 "receiveSymbol: failed to save symbol %s to library %s",
1290 libItemName.ToUTF8().data(), nickname.ToUTF8().data() );
1291 return false;
1292 }
1293
1294 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
1295 "receiveSymbol: saved symbol %s into library %s", libItemName.ToUTF8().data(),
1296 nickname.ToUTF8().data() );
1297
1298 const LIBRARY_TABLE_SCOPE scope = addToGlobal ? LIBRARY_TABLE_SCOPE::GLOBAL
1301
1302 if( placeAfterDownload )
1303 {
1304 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSymbol: placing symbol now (nickname=%s libItem=%s)",
1305 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1306 return placeDownloadedSymbol( nickname, libItemName, aError );
1307 }
1308
1309 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSymbol: saved symbol (nickname=%s libItem=%s)" ,
1310 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1311
1312 return true;
1313}
1314
1315
1316bool PANEL_REMOTE_SYMBOL::receiveFootprint( const nlohmann::json& aParams,
1317 const std::vector<uint8_t>& aPayload,
1318 wxString& aError )
1319{
1320 const wxString mode = jsonString( aParams, "mode" );
1321
1322 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveFootprint: mode=%s", mode.ToUTF8().data() );
1323
1324 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1325 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1326 {
1327 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1328 return false;
1329 }
1330
1331 const wxString contentType = jsonString( aParams, "content_type" );
1332
1333 if( !contentType.IsSameAs( wxS( "KICAD_FOOTPRINT_V1" ), false ) )
1334 {
1335 aError = _( "Unsupported footprint payload type." );
1336 return false;
1337 }
1338
1339 wxFileName baseDir;
1340
1341 if( !ensureDestinationRoot( baseDir, aError ) )
1342 return false;
1343
1344 wxFileName fpRoot = baseDir;
1345 fpRoot.AppendDir( wxS( "footprints" ) );
1346
1347 if( !fpRoot.DirExists() && !fpRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1348 {
1349 aError = wxString::Format( _( "Unable to create '%s'." ), fpRoot.GetFullPath() );
1350 return false;
1351 }
1352
1353 // Get footprint's internal name from "name" parameter
1354 wxString footprintName = jsonString( aParams, "name" );
1355 if( footprintName.IsEmpty() )
1356 footprintName = wxS( "footprint" );
1357 footprintName = sanitizeFileComponent( footprintName, wxS( "footprint" ) );
1358
1359 // Get library name from "library" parameter, fallback to "footprints"
1360 wxString libraryName = jsonString( aParams, "library" );
1361 if( libraryName.IsEmpty() )
1362 libraryName = wxS( "footprints" );
1363 libraryName.Trim( true ).Trim( false );
1364
1365 wxString sanitizedLibName = sanitizeFileComponent( libraryName, wxS( "footprints" ) );
1366 wxString libNickname = sanitizedPrefix() + wxS( "_" ) + sanitizedLibName;
1367
1368 wxFileName libDir = fpRoot;
1369 libDir.AppendDir( libNickname + wxS( ".pretty" ) );
1370
1371 if( !libDir.DirExists() && !libDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1372 {
1373 aError = wxString::Format( _( "Unable to create '%s'." ), libDir.GetFullPath() );
1374 return false;
1375 }
1376
1377 wxString fileName = footprintName;
1378
1379 if( !fileName.Lower().EndsWith( wxS( ".kicad_mod" ) ) )
1380 fileName += wxS( ".kicad_mod" );
1381
1382 wxFileName outFile( libDir );
1383 outFile.SetFullName( fileName );
1384
1385 if( !writeBinaryFile( outFile, aPayload, aError ) )
1386 return false;
1387
1388 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveFootprint: wrote footprint %s in lib %s",
1389 outFile.GetFullPath(), libNickname.ToUTF8().data() );
1390
1391 EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
1392
1393 if( !settings )
1394 {
1395 aError = _( "Unable to load schematic settings." );
1396 return false;
1397 }
1398
1399 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
1400
1401 if( !ensureFootprintLibraryEntry( libDir, libNickname, addToGlobal, aError ) )
1402 return false;
1403
1404 const LIBRARY_TABLE_SCOPE scope = addToGlobal ? LIBRARY_TABLE_SCOPE::GLOBAL
1407
1408 return true;
1409}
1410
1411
1412bool PANEL_REMOTE_SYMBOL::receive3DModel( const nlohmann::json& aParams,
1413 const std::vector<uint8_t>& aPayload,
1414 wxString& aError )
1415{
1416 const wxString mode = jsonString( aParams, "mode" );
1417
1418 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receive3DModel: mode=%s", mode.ToUTF8().data() );
1419
1420 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1421 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1422 {
1423 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1424 return false;
1425 }
1426
1427 const wxString contentType = jsonString( aParams, "content_type" );
1428
1429 if( !contentType.IsSameAs( wxS( "KICAD_3D_MODEL_STEP" ), false ) &&
1430 !contentType.IsSameAs( wxS( "KICAD_3D_MODEL_WRL" ), false ) )
1431 {
1432 aError = _( "Unsupported 3D model payload type." );
1433 return false;
1434 }
1435
1436 wxFileName baseDir;
1437
1438 if( !ensureDestinationRoot( baseDir, aError ) )
1439 return false;
1440
1441 wxFileName modelDir = baseDir;
1442 modelDir.AppendDir( sanitizedPrefix() + wxS( "_3d" ) );
1443
1444 if( !modelDir.DirExists() && !modelDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1445 {
1446 aError = wxString::Format( _( "Unable to create '%s'." ), modelDir.GetFullPath() );
1447 return false;
1448 }
1449
1450 wxString fileName = jsonString( aParams, "name" );
1451
1452 if( fileName.IsEmpty() )
1453 fileName = sanitizedPrefix() + wxS( "_model" );
1454
1455 fileName = sanitizeFileComponent( fileName, sanitizedPrefix() + wxS( "_model" ) );
1456
1457 wxString extension = wxS( ".step" );
1458
1459 if( contentType.IsSameAs( wxS( "KICAD_3D_MODEL_WRL" ), false ) )
1460 extension = wxS( ".wrl" );
1461
1462 if( !fileName.Lower().EndsWith( extension ) )
1463 fileName += extension;
1464
1465 wxFileName outFile( modelDir );
1466 outFile.SetFullName( fileName );
1467
1468 bool ok = writeBinaryFile( outFile, aPayload, aError );
1469
1470 if( ok )
1471 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receive3DModel: wrote model %s", outFile.GetFullPath() );
1472
1473 return ok;
1474}
1475
1476
1477bool PANEL_REMOTE_SYMBOL::receiveSPICEModel( const nlohmann::json& aParams,
1478 const std::vector<uint8_t>& aPayload,
1479 wxString& aError )
1480{
1481 const wxString mode = jsonString( aParams, "mode" );
1482
1483 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSPICEModel: mode=%s", mode.ToUTF8().data() );
1484
1485 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1486 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1487 {
1488 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1489 return false;
1490 }
1491
1492 const wxString contentType = jsonString( aParams, "content_type" );
1493
1494 if( !contentType.IsSameAs( wxS( "KICAD_SPICE_MODEL_V1" ), false ) )
1495 {
1496 aError = _( "Unsupported SPICE payload type." );
1497 return false;
1498 }
1499
1500 wxFileName baseDir;
1501
1502 if( !ensureDestinationRoot( baseDir, aError ) )
1503 return false;
1504
1505 wxFileName spiceDir = baseDir;
1506 spiceDir.AppendDir( sanitizedPrefix() + wxS( "_spice" ) );
1507
1508 if( !spiceDir.DirExists() && !spiceDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1509 {
1510 aError = wxString::Format( _( "Unable to create '%s'." ), spiceDir.GetFullPath() );
1511 return false;
1512 }
1513
1514 wxString fileName = jsonString( aParams, "name" );
1515
1516 if( fileName.IsEmpty() )
1517 fileName = sanitizedPrefix() + wxS( "_model.cir" );
1518
1519 fileName = sanitizeFileComponent( fileName, sanitizedPrefix() + wxS( "_model.cir" ) );
1520
1521 if( !fileName.Lower().EndsWith( wxS( ".cir" ) ) )
1522 fileName += wxS( ".cir" );
1523
1524 wxFileName outFile( spiceDir );
1525 outFile.SetFullName( fileName );
1526
1527 bool ok = writeBinaryFile( outFile, aPayload, aError );
1528
1529 if( ok )
1530 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSPICEModel: wrote spice model %s", outFile.GetFullPath() );
1531
1532 return ok;
1533}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
A bitmap button widget that behaves like an AUI toolbar item's button when it is drawn.
int ShowModal() override
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.h:113
REMOTE_SYMBOL_CONFIG m_RemoteSymbol
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Definition kiid.h:49
void ReloadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
void SetOptions(const wxString &aOptions)
void SetNickname(const wxString &aNickname)
void SetOk(bool aOk=true)
void SetType(const wxString &aType)
void SetDescription(const wxString &aDescription)
void SetURI(const wxString &aUri)
const wxString & URI() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:111
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:100
Define a library symbol object.
Definition lib_symbol.h:83
wxString sanitizeFileComponent(const wxString &aComponent, const wxString &aDefault) const
std::optional< wxString > extractUrlFromJson(const wxString &aJsonContent) const
void sendRpcMessage(const wxString &aCommand, nlohmann::json aParameters=nlohmann::json::object(), std::optional< int > aResponseTo=std::nullopt, const wxString &aStatus=wxS("OK"), const std::string &aData=std::string(), const wxString &aErrorCode=wxEmptyString, const wxString &aErrorMessage=wxEmptyString)
bool receiveSymbol(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
void onWebViewLoaded(wxWebViewEvent &aEvent)
void onKicadMessage(const wxString &aMessage)
wxString sanitizedPrefix() const
void showMessage(const wxString &aMessage)
wxString jsonString(const nlohmann::json &aObject, const char *aKey) const
bool writeBinaryFile(const wxFileName &aFile, const std::vector< uint8_t > &aData, wxString &aError) const
void handleRpcMessage(const nlohmann::json &aMessage)
std::vector< PCM_INSTALLATION_ENTRY > m_dataSources
bool receiveFootprint(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
bool ensureSymbolLibraryEntry(const wxFileName &aLibraryFile, const wxString &aNickname, bool aGlobalTable, wxString &aError) const
void respondWithError(const wxString &aCommand, int aResponseTo, const wxString &aErrorCode, const wxString &aErrorMessage)
std::optional< wxFileName > findDataSourceJson(const PCM_INSTALLATION_ENTRY &aEntry) const
PANEL_REMOTE_SYMBOL(SCH_EDIT_FRAME *aParent)
bool decompressIfNeeded(const std::string &aCompression, const std::vector< uint8_t > &aInput, std::vector< uint8_t > &aOutput, wxString &aError) const
bool decodeBase64Payload(const std::string &aMessage, std::vector< uint8_t > &aOutPayload, wxString &aError) const
wxString sanitizeForScript(const std::string &aJson) const
std::unique_ptr< LIB_SYMBOL > loadSymbolFromPayload(const std::vector< uint8_t > &aPayload, const wxString &aLibItemName, wxString &aError) const
void onConfigure(wxCommandEvent &aEvent)
bool ensureDestinationRoot(wxFileName &aOutDir, wxString &aError) const
bool loadDataSource(size_t aIndex)
bool ensureFootprintLibraryEntry(const wxFileName &aLibraryDir, const wxString &aNickname, bool aGlobalTable, wxString &aError) const
bool receiveSPICEModel(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
BITMAP_BUTTON * m_configButton
std::shared_ptr< PLUGIN_CONTENT_MANAGER > m_pcm
bool receive3DModel(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
void onDataSourceChanged(wxCommandEvent &aEvent)
bool placeDownloadedSymbol(const wxString &aNickname, const wxString &aLibItemName, wxString &aError)
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:134
Main class of Plugin and Content Manager subsystem.
Definition pcm.h:106
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
static TOOL_ACTION placeSymbol
Definition sch_actions.h:66
Schematic editor (Eeschema) main window.
Schematic symbol object.
Definition sch_symbol.h:76
void AutoplaceFields(SCH_SCREEN *aScreen, AUTOPLACE_ALGO aAlgo) override
Automatically orient all the fields in the symbol.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
SAVE_T SaveSymbol(const wxString &aNickname, const LIB_SYMBOL *aSymbol, bool aOverwrite=true)
Write aSymbol to an existing library given by aNickname.
Master controller class:
bool PostAction(const std::string &aActionName, T aParam)
Run the specified action after the current action (coroutine) ends.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:558
The common library.
#define _(s)
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PROJECT & Prj()
Definition kicad.cpp:637
LIBRARY_TABLE_SCOPE
STL namespace.
#define REMOTE_SYMBOL_SESSION_VERSION
SETTINGS_MANAGER * GetSettingsManager()
@ PT_DATASOURCE
Definition pcm_data.h:47
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ AUTOPLACE_AUTO
Definition sch_item.h:70
T * GetAppSettings(const char *aFilename)
Definition pcm_data.h:158
PCM_PACKAGE package
Definition pcm_data.h:159
wxString identifier
Definition pcm_data.h:118
wxString name
Definition pcm_data.h:115
std::string path
wxString result
Test unit parsing edge cases and error handling.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:39