KiCad PCB EDA Suite
Loading...
Searching...
No Matches
remote_symbol_import_job.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, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
21
23
24#include <eeschema_settings.h>
25#include <io/io_mgr.h>
26#include <lib_id.h>
27#include <lib_symbol.h>
30#include <pgm_base.h>
31#include <project_sch.h>
33#include <sch_edit_frame.h>
34#include <sch_io/sch_io.h>
35#include <sch_io/sch_io_mgr.h>
37#include <string_view>
38
39#include <wx/intl.h>
40
41
42namespace
43{
44bool validateSymbolPayload( const std::vector<uint8_t>& aPayload, const wxString& aExpectedSymbolName,
45 wxString& aError )
46{
47 if( aExpectedSymbolName.IsEmpty() )
48 return true;
49
50 if( aPayload.empty() )
51 {
52 aError = _( "Downloaded symbol payload was empty." );
53 return false;
54 }
55
56 std::string_view payload( reinterpret_cast<const char*>( aPayload.data() ), aPayload.size() );
57
58 if( payload.find( "(kicad_symbol_lib" ) == std::string_view::npos )
59 {
60 aError = _( "Downloaded symbol payload was not a KiCad symbol library." );
61 return false;
62 }
63
64 const std::string symbolToken = wxString::Format( wxS( "(symbol \"%s\"" ), aExpectedSymbolName ).ToStdString();
65
66 if( payload.find( symbolToken ) == std::string_view::npos )
67 {
68 aError = wxString::Format( _( "Downloaded symbol payload did not include expected symbol '%s'." ),
69 aExpectedSymbolName );
70 return false;
71 }
72
73 return true;
74}
75} // namespace
76
77
79 REMOTE_SYMBOL_DOWNLOAD_MANAGER* aDownloader ) :
80 m_frame( aFrame ),
81 m_downloader( aDownloader )
82{
83 if( !m_downloader )
84 {
85 m_ownedDownloader = std::make_unique<REMOTE_SYMBOL_DOWNLOAD_MANAGER>();
87 }
88}
89
90
92 const REMOTE_SYMBOL_IMPORT_CONTEXT& aContext,
93 const REMOTE_PROVIDER_PART_MANIFEST& aManifest,
94 bool aPlaceSymbol, wxString& aError )
95{
96 aError.clear();
97
99
100 if( !settings )
101 {
102 aError = _( "Unable to load schematic settings." );
103 return false;
104 }
105
106 wxFileName baseDir;
107
108 if( !EnsureRemoteDestinationRoot( baseDir, aError ) )
109 return false;
110
111 long long remainingBudget = aProvider.max_download_bytes;
112 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
113 const bool strictLibraryTables = m_frame != nullptr;
114 const wxString prefix = RemoteLibraryPrefix();
116 bool importedSymbol = false;
117 wxString placedNickname;
118 wxString placedSymbolName;
119
120 // Sort asset indices: footprints first, then 3D/SPICE, then symbols. The symbol's
121 // Footprint field references a LIB_ID that must already exist on disk and (when
122 // running interactive) be registered in the footprint library table by the time the
123 // symbol file is written, otherwise CvPcb / placement-time resolution misses it.
124 std::vector<size_t> footprintIdx, otherIdx, symbolIdx;
125
126 for( size_t i = 0; i < aManifest.assets.size(); ++i )
127 {
128 const wxString& type = aManifest.assets[i].asset_type;
129
130 if( type == wxS( "footprint" ) )
131 footprintIdx.push_back( i );
132 else if( type == wxS( "symbol" ) )
133 symbolIdx.push_back( i );
134 else
135 otherIdx.push_back( i );
136 }
137
138 std::vector<LIB_ID> footprintLinks;
139
140 auto downloadAsset = [&]( size_t i, REMOTE_SYMBOL_FETCHED_ASSET& fetched ) -> bool
141 {
142 if( !downloader().DownloadAndVerify( aProvider, aManifest.assets[i], remainingBudget, fetched, aError ) )
143 return false;
144
145 remainingBudget -= aManifest.assets[i].size_bytes;
146 return true;
147 };
148
149 // --- Footprints ---
150 for( size_t i : footprintIdx )
151 {
152 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
154
155 if( !downloadAsset( i, fetched ) )
156 return false;
157
158 wxFileName fpRoot = baseDir;
159 fpRoot.AppendDir( wxS( "footprints" ) );
160
161 // Resolve logical names with the same fallbacks the original code used.
162 const wxString resolvedLib = asset.target_library.IsEmpty() ? asset.name : asset.target_library;
163 const wxString resolvedName = asset.target_name.IsEmpty() ? asset.name : asset.target_name;
164 const LIB_ID fpLibId = BuildRemoteLibId( resolvedLib, resolvedName );
165 const wxString nickname = fpLibId.GetUniStringLibNickname();
166
167 wxFileName libDir = fpRoot;
168 libDir.AppendDir( nickname + wxS( ".pretty" ) );
169
170 wxString fileName = fpLibId.GetUniStringLibItemName() + wxS( ".kicad_mod" );
171
172 wxFileName outFile( libDir );
173 outFile.SetFullName( fileName );
174
175 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
176 return false;
177
178 if( strictLibraryTables )
179 {
180 if( !EnsureRemoteLibraryEntry( LIBRARY_TABLE_TYPE::FOOTPRINT, libDir, nickname, addToGlobal, true,
181 aError ) )
182 {
183 return false;
184 }
185
187 libMgr.ReloadLibraryEntry( LIBRARY_TABLE_TYPE::FOOTPRINT, nickname, scope );
188 }
189
190 footprintLinks.push_back( fpLibId );
191 }
192
193 // --- 3D models, SPICE ---
194 for( size_t i : otherIdx )
195 {
196 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
198
199 if( !downloadAsset( i, fetched ) )
200 return false;
201
202 if( asset.asset_type == wxS( "3dmodel" ) )
203 {
204 wxFileName modelDir = baseDir;
205 modelDir.AppendDir( prefix + wxS( "_3d" ) );
206
207 wxString fileName = SanitizeRemoteFileComponent( asset.target_name.IsEmpty() ? asset.name
208 : asset.target_name,
209 prefix + wxS( "_model" ) );
210
211 wxFileName outFile( modelDir );
212 outFile.SetFullName( fileName );
213
214 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
215 return false;
216 }
217 else if( asset.asset_type == wxS( "spice" ) )
218 {
219 wxFileName spiceDir = baseDir;
220 spiceDir.AppendDir( prefix + wxS( "_spice" ) );
221
222 wxString fileName = SanitizeRemoteFileComponent( asset.target_name.IsEmpty() ? asset.name
223 : asset.target_name,
224 prefix + wxS( "_model.cir" ) );
225
226 if( !fileName.Lower().EndsWith( wxS( ".cir" ) ) )
227 fileName += wxS( ".cir" );
228
229 wxFileName outFile( spiceDir );
230 outFile.SetFullName( fileName );
231
232 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
233 return false;
234 }
235 }
236
237 // --- Symbols ---
238 for( size_t i : symbolIdx )
239 {
240 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
242
243 if( !downloadAsset( i, fetched ) )
244 return false;
245
246 wxFileName symbolDir = baseDir;
247 symbolDir.AppendDir( wxS( "symbols" ) );
248
249 const wxString libraryName = SanitizeRemoteFileComponent( asset.target_library.IsEmpty()
250 ? aContext.library_name
251 : asset.target_library,
252 wxS( "symbols" ), true );
253 const wxString symbolName = asset.target_name.IsEmpty() ? aContext.symbol_name : asset.target_name;
254 const wxString nickname = prefix + wxS( "_" ) + libraryName;
255
256 wxFileName outFile( symbolDir );
257 outFile.SetFullName( nickname + wxS( ".kicad_sym" ) );
258
259 if( strictLibraryTables && symbolName.IsEmpty() )
260 {
261 aError = _( "No symbol asset specified." );
262 return false;
263 }
264
265 if( !validateSymbolPayload( fetched.payload, symbolName, aError ) )
266 return false;
267
268 // Deserialize → mutate → save so the persisted symbol's Footprint field is
269 // already pointing at the local LIB_IDs of the bundle's footprints.
270 std::unique_ptr<LIB_SYMBOL> loaded = LoadRemoteSymbolFromPayload( fetched.payload, symbolName, aError );
271
272 if( loaded )
273 {
274 loaded->SetName( symbolName );
275 LIB_ID savedId;
276 savedId.SetLibNickname( nickname );
277 savedId.SetLibItemName( symbolName );
278 loaded->SetLibId( savedId );
279
280 ApplyFootprintLinks( *loaded, footprintLinks );
281 }
282
283 symbolDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
284
285 if( strictLibraryTables )
286 {
287 if( !loaded )
288 return false;
289
291
292 if( !adapter )
293 {
294 aError = _( "Unable to access the symbol library manager." );
295 return false;
296 }
297
298 if( !SaveRemoteSymbolToLibrary( *adapter, outFile, nickname, addToGlobal, std::move( loaded ), aError ) )
299 return false;
300 }
301 else
302 {
303 // Headless: write the raw payload to disk so the SCH plugin's library cache
304 // has an existing file to load, then re-save through the plugin to replace
305 // the symbol entry with the mutated (link-applied) copy. When there are no
306 // links to apply, the raw payload on disk is already correct and we skip
307 // the second save.
308 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
309 return false;
310
311 if( !footprintLinks.empty() )
312 {
313 try
314 {
315 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
316
317 if( !plugin )
318 {
319 aError = _( "Unable to access the KiCad symbol plugin." );
320 return false;
321 }
322
323 plugin->SaveSymbol( outFile.GetFullPath(), std::move( loaded ) );
324 }
325 catch( const IO_ERROR& e )
326 {
327 aError = wxString::Format( _( "Unable to save the downloaded symbol: %s" ), e.What() );
328 return false;
329 }
330 }
331 }
332
333 importedSymbol = loaded != nullptr;
334 placedNickname = nickname;
335 placedSymbolName = symbolName;
336 }
337
338 if( aPlaceSymbol )
339 {
340 if( !importedSymbol )
341 {
342 aError = _( "No symbol asset was available to place." );
343 return false;
344 }
345
346 if( !PlaceRemoteDownloadedSymbol( m_frame, placedNickname, placedSymbolName, aError ) )
347 return false;
348 }
349
350 return true;
351}
352
353
REMOTE_PROVIDER_SETTINGS 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()
void ReloadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
Synchronously recreates the plugin for and reloads the given library.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:124
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:113
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:125
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
const REMOTE_SYMBOL_DOWNLOAD_MANAGER & downloader() const
std::unique_ptr< REMOTE_SYMBOL_DOWNLOAD_MANAGER > m_ownedDownloader
REMOTE_SYMBOL_DOWNLOAD_MANAGER * m_downloader
REMOTE_SYMBOL_IMPORT_JOB(SCH_EDIT_FRAME *aFrame, REMOTE_SYMBOL_DOWNLOAD_MANAGER *aDownloader=nullptr)
bool Import(const REMOTE_PROVIDER_METADATA &aProvider, const REMOTE_SYMBOL_IMPORT_CONTEXT &aContext, const REMOTE_PROVIDER_PART_MANIFEST &aManifest, bool aPlaceSymbol, wxString &aError)
Schematic editor (Eeschema) main window.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
#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
LIBRARY_TABLE_SCOPE
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
void ApplyFootprintLinks(LIB_SYMBOL &aSymbol, const std::vector< LIB_ID > &aLinks)
Apply a list of footprint LIB_IDs to a symbol about to be saved.
std::unique_ptr< LIB_SYMBOL > LoadRemoteSymbolFromPayload(const std::vector< uint8_t > &aPayload, const wxString &aLibItemName, wxString &aError)
Deserialize a remote-downloaded symbol payload into a LIB_SYMBOL.
LIB_ID BuildRemoteLibId(const wxString &aResolvedLibrary, const wxString &aResolvedItemName)
Build the local LIB_ID for a remote-downloaded library item.
bool PlaceRemoteDownloadedSymbol(SCH_EDIT_FRAME *aFrame, const wxString &aNickname, const wxString &aLibItemName, wxString &aError)
Place a symbol from a remote download into the schematic editor.
bool EnsureRemoteLibraryEntry(LIBRARY_TABLE_TYPE aTableType, const wxFileName &aLibraryPath, const wxString &aNickname, bool aGlobalTable, bool aStrict, wxString &aError)
Add or update a library table entry for a remote download library.
bool WriteRemoteBinaryFile(const wxFileName &aOutput, const std::vector< uint8_t > &aPayload, wxString &aError)
Write binary data to a file, creating parent directories as needed.
bool SaveRemoteSymbolToLibrary(SYMBOL_LIBRARY_ADAPTER &aAdapter, const wxFileName &aLibraryFile, const wxString &aNickname, bool aGlobalTable, std::unique_ptr< LIB_SYMBOL > aSymbol, wxString &aError)
Save a downloaded symbol into the remote symbol library aNickname backed by aLibraryFile.
bool EnsureRemoteDestinationRoot(wxFileName &aOutDir, wxString &aError)
Resolve and create the configured destination root directory for remote symbol downloads.
wxString RemoteLibraryPrefix()
Return the configured (or default) library prefix for remote downloads, sanitized for use as a filena...
wxString SanitizeRemoteFileComponent(const wxString &aValue, const wxString &aDefault, bool aLower)
Replace non-alphanumeric characters (other than _ - .) with underscores.
T * GetAppSettings(const char *aFilename)
std::vector< REMOTE_PROVIDER_PART_ASSET > assets