1054 if( !aMessage.is_object() )
1057 const wxString command =
jsonString( aMessage,
"command" );
1059 if( command.IsEmpty() )
1062 auto messageIdIt = aMessage.find(
"message_id" );
1064 if( messageIdIt == aMessage.end() || !messageIdIt->is_number_integer() )
1067 const int messageId = messageIdIt->get<
int>();
1069 const int version = aMessage.value(
"version", 0 );
1074 wxString::Format(
_(
"Unsupported RPC version %d." ), version ) );
1078 const wxString sessionId =
jsonString( aMessage,
"session_id" );
1080 if( sessionId.IsEmpty() )
1083 _(
"Missing session identifier." ) );
1087 if( !sessionId.IsSameAs(
m_sessionId.AsString() ) )
1088 wxLogWarning(
"Remote symbol RPC session mismatch (expected %s, got %s).",
1091 const wxString status =
jsonString( aMessage,
"status" );
1093 if( status.IsSameAs( wxS(
"ERROR" ),
false ) )
1095 wxLogWarning(
"Remote symbol RPC error (%s): %s",
jsonString( aMessage,
"error_code" ),
1100 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
"handleRpcMessage: command=%s message_id=%d status=%s session=%s",
1101 command.ToUTF8().data(), messageId, status.ToUTF8().data(), sessionId.ToUTF8().data() );
1103 nlohmann::json params = nlohmann::json::object();
1104 auto paramsIt = aMessage.find(
"parameters" );
1106 if( paramsIt != aMessage.end() && paramsIt->is_object() )
1109 const std::string data = aMessage.value(
"data", std::string() );
1111 if( command == wxS(
"NEW_SESSION" ) )
1113 nlohmann::json reply = nlohmann::json::object();
1114 reply[
"client_name"] =
"KiCad";
1120 else if( command == wxS(
"GET_KICAD_VERSION" ) )
1122 nlohmann::json reply = nlohmann::json::object();
1127 else if( command == wxS(
"LIST_SUPPORTED_VERSIONS" ) )
1129 nlohmann::json reply = nlohmann::json::object();
1134 else if( command == wxS(
"CAPABILITIES" ) )
1136 nlohmann::json reply = nlohmann::json::object();
1137 reply[
"commands"] = {
"NEW_SESSION",
"GET_KICAD_VERSION",
"LIST_SUPPORTED_VERSIONS",
1138 "CAPABILITIES",
"PING",
"PONG",
"REMOTE_LOGIN",
"LOGOUT",
"DL_SYMBOL",
"DL_COMPONENT",
"DL_FOOTPRINT",
1139 "DL_SPICE",
"DL_3DMODEL" };
1140 reply[
"compression"] = {
"NONE",
"ZSTD" };
1141 reply[
"max_message_size"] = 0;
1145 else if( command == wxS(
"PING" ) )
1147 nlohmann::json reply = nlohmann::json::object();
1149 if( params.contains(
"nonce" ) )
1150 reply[
"nonce"] = params[
"nonce"];
1155 else if( command == wxS(
"PONG" ) )
1159 else if( command == wxS(
"REMOTE_LOGIN" ) )
1164 else if( command == wxS(
"LOGOUT" ) )
1182 const wxString compression =
jsonString( params,
"compression" );
1184 if( command.StartsWith( wxS(
"DL_" ) ) )
1186 if( compression.IsEmpty() )
1188 respondWithError( command, messageId, wxS(
"INVALID_PARAMETERS" ),
_(
"Missing compression metadata." ) );
1192 std::vector<uint8_t> decoded;
1201 std::vector<uint8_t> payload;
1202 wxScopedCharBuffer compUtf8 = compression.ToUTF8();
1203 std::string compressionStr = compUtf8 ? std::string( compUtf8.data() ) : std::string();
1211 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
"handleRpcMessage: decoded size=%zu decompressed size=%zu command=%s",
1212 decoded.size(), payload.size(), command.ToUTF8().data() );
1214 nlohmann::json responseParams = nlohmann::json::object();
1217 if( command == wxS(
"DL_SYMBOL" ) )
1219 else if( command == wxS(
"DL_COMPONENT" ) )
1221 else if( command == wxS(
"DL_FOOTPRINT" ) )
1223 else if( command == wxS(
"DL_3DMODEL" ) )
1225 else if( command == wxS(
"DL_SPICE" ) )
1230 wxString::Format(
_(
"Command '%s' is not supported." ), command ) );
1235 sendRpcMessage( command, std::move( responseParams ), messageId );
1238 error.IsEmpty() ?
_(
"Unable to store payload." ) : error );
1244 wxString::Format(
_(
"Command '%s' is not supported." ), command ) );
1507 const std::vector<uint8_t>& aPayload,
1510 const wxString mode =
jsonString( aParams,
"mode" );
1511 const bool placeAfterDownload = mode.IsSameAs( wxS(
"PLACE" ),
false );
1513 if( !mode.IsEmpty() && !mode.IsSameAs( wxS(
"SAVE" ),
false )
1514 && !mode.IsSameAs( wxS(
"PLACE" ),
false ) )
1516 aError = wxString::Format(
_(
"Unsupported transfer mode '%s'." ), mode );
1520 const wxString contentType =
jsonString( aParams,
"content_type" );
1522 if( !contentType.IsSameAs( wxS(
"KICAD_SYMBOL_V1" ),
false ) )
1524 aError =
_(
"Unsupported symbol payload type." );
1530 aError =
_(
"No schematic editor is available to store symbols." );
1538 aError =
_(
"Unable to load schematic settings." );
1549 wxFileName symDir = baseDir;
1550 symDir.AppendDir( wxS(
"symbols" ) );
1552 if( !symDir.DirExists() && !symDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1554 aError = wxString::Format(
_(
"Unable to create '%s'." ), symDir.GetFullPath() );
1558 wxString libItemName =
jsonString( aParams,
"name" );
1559 wxString baseName = libItemName;
1561 if( baseName.IsEmpty() )
1564 if( baseName.IsEmpty() )
1565 baseName = wxS(
"symbol" );
1567 baseName.Trim(
true ).Trim(
false );
1569 if( libItemName.IsEmpty() )
1570 libItemName = baseName;
1574 if( libItemName.IsEmpty() )
1575 libItemName = sanitizedName;
1577 const wxString nickname =
sanitizedPrefix() + wxS(
"_sym_" ) + sanitizedName;
1579 wxFileName outFile( symDir );
1580 outFile.SetFullName( nickname + wxS(
".kicad_sym" ) );
1589 aError =
_(
"Unable to access the symbol library manager." );
1594 std::optional<LIB_STATUS> libStatus = adapter->
LoadOne( nickname );
1595 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
1596 "receiveSymbol: LoadOne(%s) returned %s",
1597 nickname.ToUTF8().data(),
1598 libStatus.has_value() ?
"valid status" :
"nullopt" );
1600 std::unique_ptr<LIB_SYMBOL> downloadedSymbol =
loadSymbolFromPayload( aPayload, libItemName, aError );
1602 if( !downloadedSymbol )
1604 if( aError.IsEmpty() )
1605 aError =
_(
"Unable to parse the downloaded symbol." );
1610 downloadedSymbol->SetName( libItemName );
1615 downloadedSymbol->SetLibId( savedId );
1617 if( adapter->
SaveSymbol( nickname, downloadedSymbol.get(),
true )
1620 aError =
_(
"Unable to save the downloaded symbol." );
1621 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
1622 "receiveSymbol: failed to save symbol %s to library %s",
1623 libItemName.ToUTF8().data(), nickname.ToUTF8().data() );
1628 downloadedSymbol.release();
1630 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
1631 "receiveSymbol: saved symbol %s into library %s", libItemName.ToUTF8().data(),
1632 nickname.ToUTF8().data() );
1640 if( placeAfterDownload )
1642 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
"receiveSymbol: placing symbol now (nickname=%s libItem=%s)",
1643 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1654 wxLogTrace( wxS(
"KI_TRACE_REMOTE_SYMBOL" ),
"receiveSymbol: saved symbol (nickname=%s libItem=%s)" ,
1655 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1876 const std::vector<uint8_t>& aPayload,
1877 wxString& aError, nlohmann::json* aResponseParams )
1879 nlohmann::json components;
1883 components = nlohmann::json::parse( aPayload.begin(), aPayload.end() );
1885 catch(
const std::exception& e )
1887 aError = wxString::Format(
_(
"Failed to parse component list: %s" ), e.what() );
1891 if( !components.is_array() )
1893 aError =
_(
"Component list must be an array." );
1897 if( components.empty() )
1899 aError =
_(
"Component list was empty." );
1906 if( libNickname.IsEmpty() )
1907 libNickname = wxS(
"Remote" );
1914 auto ensureDirectory = [&]( wxFileName& aDir ) ->
bool
1916 if( aDir.DirExists() )
1919 if( !aDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1921 aError = wxString::Format(
_(
"Unable to create '%s'." ), aDir.GetFullPath() );
1928 wxFileName librariesDir( baseDir );
1929 librariesDir.AppendDir( wxS(
"symbols" ) );
1931 if( !ensureDirectory( librariesDir ) )
1934 wxFileName symbolLib( librariesDir );
1935 symbolLib.SetFullName( libNickname + wxS(
".kicad_sym" ) );
1937 wxFileName footprintsDir( baseDir );
1938 footprintsDir.AppendDir( wxS(
"footprints" ) );
1940 if( !ensureDirectory( footprintsDir ) )
1943 wxFileName footprintLibDir( footprintsDir );
1944 footprintLibDir.AppendDir( libNickname + wxS(
".pretty" ) );
1946 if( !ensureDirectory( footprintLibDir ) )
1949 wxFileName modelDir( baseDir );
1950 modelDir.AppendDir( wxS(
"3dmodels" ) );
1952 if( !ensureDirectory( modelDir ) )
1955 modelDir.AppendDir( libNickname );
1957 if( !ensureDirectory( modelDir ) )
1964 aError =
_(
"Unable to load schematic settings." );
1970 std::unique_ptr<SCH_IO> symbolPlugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
1974 aError =
_(
"Unable to access the KiCad symbol plugin." );
1978 struct COMPONENT_PAYLOAD
1981 wxString declaredName;
1982 wxString sanitizedName;
1984 wxString payloadSymbolName;
1985 std::string checksum;
1986 std::vector<uint8_t> content;
1990 std::vector<COMPONENT_PAYLOAD> prepared;
1991 prepared.reserve( components.size() );
1993 for(
const nlohmann::json& component : components )
1995 if( !component.is_object() )
1997 aError =
_(
"Component entries must be objects." );
2001 COMPONENT_PAYLOAD entry;
2002 entry.type = component.value(
"type",
"" );
2004 if( entry.type.empty() )
2006 aError =
_(
"Component entry was missing a type." );
2010 std::transform( entry.type.begin(), entry.type.end(), entry.type.begin(),
2011 [](
unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
2013 entry.declaredName = wxString::FromUTF8( component.value(
"name", entry.type ).c_str() );
2014 entry.declaredName.Trim(
true ).Trim(
false );
2016 wxString fallback =
sanitizedPrefix() + wxS(
"_" ) + wxString::FromUTF8( entry.type );
2019 if( entry.sanitizedName.IsEmpty() )
2020 entry.sanitizedName = fallback;
2022 entry.finalName = entry.sanitizedName;
2023 entry.payloadSymbolName = entry.declaredName.IsEmpty() ? entry.sanitizedName : entry.declaredName;
2025 const std::string encoded = component.value(
"content", std::string() );
2030 const std::string compression = component.value(
"compression", std::string() );
2032 if( !compression.empty() && compression !=
"NONE" )
2034 std::vector<uint8_t> decompressed;
2039 entry.content = std::move( decompressed );
2042 entry.checksum = component.value(
"checksum", std::string() );
2044 if( entry.checksum.empty() )
2045 entry.checksum = HashBuffer( entry.content );
2047 prepared.emplace_back( std::move( entry ) );
2050 nlohmann::json skipped = nlohmann::json::array();
2051 nlohmann::json renamedReport = nlohmann::json::array();
2052 std::map<std::string, std::map<wxString, wxString>> renames;
2053 std::set<wxString> reservedSymbolNames;
2054 std::set<wxString> reservedFootprintNames;
2055 std::set<wxString> reservedModelNames;
2057 auto recordRename = [&](
const std::string& aType,
const wxString& aFrom,
const wxString& aTo )
2062 renames[aType][aFrom] = aTo;
2064 renamedReport.push_back( { {
"type", aType },
2065 {
"from", aFrom.ToStdString() },
2066 {
"to", aTo.ToStdString() } } );
2069 auto recordSkip = [&](
const std::string& aType,
const wxString& aName )
2071 skipped.push_back( { {
"type", aType }, {
"name", aName.ToStdString() } } );
2074 auto footprintPathFor = [&](
const wxString& aName )
2076 wxFileName fn( footprintLibDir );
2077 fn.SetFullName( aName + wxS(
".kicad_mod" ) );
2081 auto modelPathFor = [&](
const wxString& aName )
2083 wxFileName fn( modelDir );
2084 fn.SetFullName( aName );
2088 for( COMPONENT_PAYLOAD& entry : prepared )
2090 if( entry.type ==
"footprint" )
2092 wxFileName existing = footprintPathFor( entry.sanitizedName );
2093 std::string localChecksum;
2095 if( HashFile( existing, localChecksum ) && localChecksum == entry.checksum )
2098 recordSkip( entry.type, entry.sanitizedName );
2102 wxString candidate = entry.sanitizedName;
2105 auto collides = [&](
const wxString&
name )
2107 if( reservedFootprintNames.contains(
name ) )
2110 return footprintPathFor(
name ).FileExists();
2113 while( collides( candidate ) )
2114 candidate = AppendNumericSuffix( entry.sanitizedName, suffix++ );
2116 reservedFootprintNames.insert( candidate );
2117 recordRename( entry.type, entry.sanitizedName, candidate );
2118 entry.finalName = candidate;
2120 else if( entry.type ==
"3dmodel" )
2122 wxFileName existing = modelPathFor( entry.sanitizedName );
2123 std::string localChecksum;
2125 if( HashFile( existing, localChecksum ) && localChecksum == entry.checksum )
2128 recordSkip( entry.type, entry.sanitizedName );
2132 wxString candidate = entry.sanitizedName;
2135 auto collides = [&](
const wxString&
name )
2137 if( reservedModelNames.contains(
name ) )
2140 return modelPathFor(
name ).FileExists();
2143 while( collides( candidate ) )
2144 candidate = AppendNumericSuffixToFilename( entry.sanitizedName, suffix++ );
2146 reservedModelNames.insert( candidate );
2147 recordRename( entry.type, entry.sanitizedName, candidate );
2148 entry.finalName = candidate;
2150 else if( entry.type ==
"symbol" )
2152 std::string localChecksum;
2154 if( ComputeSymbolChecksum( symbolPlugin.get(), symbolLib, entry.sanitizedName, localChecksum )
2155 && localChecksum == entry.checksum )
2158 recordSkip( entry.type, entry.sanitizedName );
2162 wxString candidate = entry.sanitizedName;
2165 auto exists = [&](
const wxString&
name )
2167 if( reservedSymbolNames.contains(
name ) )
2170 std::unique_ptr<LIB_SYMBOL> symbol = TryCloneSymbol( symbolPlugin.get(), symbolLib,
name );
2171 return static_cast<bool>( symbol );
2174 while( exists( candidate ) )
2175 candidate = AppendNumericSuffix( entry.sanitizedName, suffix++ );
2177 reservedSymbolNames.insert( candidate );
2178 recordRename( entry.type, entry.sanitizedName, candidate );
2179 entry.finalName = candidate;
2183 aError = wxString::Format(
_(
"Unsupported component type '%s'." ),
2184 wxString::FromUTF8( entry.type ) );
2189 bool footprintLibraryReady =
false;
2190 bool symbolLibraryReady =
false;
2192 auto ensureFootprintLibrary = [&]() ->
bool
2194 if( footprintLibraryReady )
2200 footprintLibraryReady =
true;
2204 auto ensureSymbolLibrary = [&]() ->
bool
2206 if( symbolLibraryReady )
2212 symbolLibraryReady =
true;
2216 for( COMPONENT_PAYLOAD& entry : prepared )
2221 if( entry.type ==
"footprint" )
2223 if( !ensureFootprintLibrary() )
2226 if( !renames[
"3dmodel"].
empty() )
2228 std::string contentStr( entry.content.begin(), entry.content.end() );
2230 for(
const auto& [oldModel, newModel] : renames[
"3dmodel"] )
2232 const std::string from = oldModel.ToStdString();
2233 const std::string to = newModel.ToStdString();
2234 size_t pos = contentStr.find( from );
2236 while( pos != std::string::npos )
2238 contentStr.replace( pos, from.size(), to );
2239 pos = contentStr.find( from, pos + to.size() );
2243 entry.content.assign( contentStr.begin(), contentStr.end() );
2246 wxFileName target = footprintPathFor( entry.finalName );
2251 else if( entry.type ==
"3dmodel" )
2253 wxFileName target = modelPathFor( entry.finalName );
2255 if( !target.GetPath().IsEmpty() )
2257 wxFileName parent( target.GetPath(), wxEmptyString );
2259 if( !parent.DirExists() && !parent.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
2261 aError = wxString::Format(
_(
"Unable to create '%s'." ), parent.GetFullPath() );
2269 else if( entry.type ==
"symbol" )
2271 if( !ensureSymbolLibrary() )
2274 if( !renames[
"footprint"].
empty() )
2276 std::string contentStr( entry.content.begin(), entry.content.end() );
2278 for(
const auto& [oldFp, newFp] : renames[
"footprint"] )
2280 const std::string from = oldFp.ToStdString();
2281 const std::string to = newFp.ToStdString();
2282 size_t pos = contentStr.find( from );
2284 while( pos != std::string::npos )
2286 contentStr.replace( pos, from.size(), to );
2287 pos = contentStr.find( from, pos + to.size() );
2291 entry.content.assign( contentStr.begin(), contentStr.end() );
2295 entry.payloadSymbolName,
2301 if( entry.finalName != entry.sanitizedName )
2302 symbol->SetName( entry.finalName );
2306 if( !symbolLib.FileExists() )
2307 symbolPlugin->SaveLibrary( symbolLib.GetFullPath() );
2309 symbolPlugin->SaveSymbol( symbolLib.GetFullPath(), symbol.get() );
2314 aError = ioe.
What();
2320 if( aResponseParams )
2322 nlohmann::json response = nlohmann::json::object();
2324 if( !skipped.empty() )
2325 response[
"skipped"] = skipped;
2327 if( !renamedReport.empty() )
2328 response[
"renamed"] = renamedReport;
2330 *aResponseParams = std::move( response );