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 along
17 * with this program. If not, see <http://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 =
65 wxString::Format( wxS( "(symbol \"%s\"" ), aExpectedSymbolName ).ToStdString();
66
67 if( payload.find( symbolToken ) == std::string_view::npos )
68 {
69 aError = wxString::Format(
70 _( "Downloaded symbol payload did not include expected symbol '%s'." ),
71 aExpectedSymbolName );
72 return false;
73 }
74
75 return true;
76}
77} // namespace
78
79
81 REMOTE_SYMBOL_DOWNLOAD_MANAGER* aDownloader ) :
82 m_frame( aFrame ),
83 m_downloader( aDownloader )
84{
85 if( !m_downloader )
86 {
87 m_ownedDownloader = std::make_unique<REMOTE_SYMBOL_DOWNLOAD_MANAGER>();
89 }
90}
91
92
94 const REMOTE_SYMBOL_IMPORT_CONTEXT& aContext,
95 const REMOTE_PROVIDER_PART_MANIFEST& aManifest,
96 bool aPlaceSymbol, wxString& aError )
97{
98 aError.clear();
99
101
102 if( !settings )
103 {
104 aError = _( "Unable to load schematic settings." );
105 return false;
106 }
107
108 wxFileName baseDir;
109
110 if( !EnsureRemoteDestinationRoot( baseDir, aError ) )
111 return false;
112
113 long long remainingBudget = aProvider.max_download_bytes;
114 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
115 const bool strictLibraryTables = m_frame != nullptr;
116 const wxString prefix = RemoteLibraryPrefix();
117 const LIBRARY_TABLE_SCOPE scope =
119 bool importedSymbol = false;
120 wxString placedNickname;
121 wxString placedSymbolName;
122
123 // Sort asset indices: footprints first, then 3D/SPICE, then symbols. The symbol's
124 // Footprint field references a LIB_ID that must already exist on disk and (when
125 // running interactive) be registered in the footprint library table by the time the
126 // symbol file is written, otherwise CvPcb / placement-time resolution misses it.
127 std::vector<size_t> footprintIdx, otherIdx, symbolIdx;
128
129 for( size_t i = 0; i < aManifest.assets.size(); ++i )
130 {
131 const wxString& type = aManifest.assets[i].asset_type;
132
133 if( type == wxS( "footprint" ) )
134 footprintIdx.push_back( i );
135 else if( type == wxS( "symbol" ) )
136 symbolIdx.push_back( i );
137 else
138 otherIdx.push_back( i );
139 }
140
141 std::vector<LIB_ID> footprintLinks;
142
143 auto downloadAsset = [&]( size_t i, REMOTE_SYMBOL_FETCHED_ASSET& fetched ) -> bool
144 {
145 if( !downloader().DownloadAndVerify( aProvider, aManifest.assets[i], remainingBudget,
146 fetched, aError ) )
147 return false;
148
149 remainingBudget -= aManifest.assets[i].size_bytes;
150 return true;
151 };
152
153 // --- Footprints ---
154 for( size_t i : footprintIdx )
155 {
156 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
158
159 if( !downloadAsset( i, fetched ) )
160 return false;
161
162 wxFileName fpRoot = baseDir;
163 fpRoot.AppendDir( wxS( "footprints" ) );
164
165 // Resolve logical names with the same fallbacks the original code used.
166 const wxString resolvedLib =
167 asset.target_library.IsEmpty() ? asset.name : asset.target_library;
168 const wxString resolvedName =
169 asset.target_name.IsEmpty() ? asset.name : asset.target_name;
170
171 const LIB_ID fpLibId = BuildRemoteLibId( resolvedLib, resolvedName );
172 const wxString nickname = fpLibId.GetUniStringLibNickname();
173
174 wxFileName libDir = fpRoot;
175 libDir.AppendDir( nickname + wxS( ".pretty" ) );
176
177 wxString fileName = fpLibId.GetUniStringLibItemName();
178
179 if( !fileName.Lower().EndsWith( wxS( ".kicad_mod" ) ) )
180 fileName += wxS( ".kicad_mod" );
181
182 wxFileName outFile( libDir );
183 outFile.SetFullName( fileName );
184
185 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
186 return false;
187
188 if( strictLibraryTables )
189 {
191 addToGlobal, true, aError ) )
192 return false;
193
195 libMgr.ReloadLibraryEntry( LIBRARY_TABLE_TYPE::FOOTPRINT, nickname, scope );
197 }
198
199 footprintLinks.push_back( fpLibId );
200 }
201
202 // --- 3D models, SPICE ---
203 for( size_t i : otherIdx )
204 {
205 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
207
208 if( !downloadAsset( i, fetched ) )
209 return false;
210
211 if( asset.asset_type == wxS( "3dmodel" ) )
212 {
213 wxFileName modelDir = baseDir;
214 modelDir.AppendDir( prefix + wxS( "_3d" ) );
215
216 wxString fileName = SanitizeRemoteFileComponent(
217 asset.target_name.IsEmpty() ? asset.name : asset.target_name,
218 prefix + wxS( "_model" ) );
219
220 wxFileName outFile( modelDir );
221 outFile.SetFullName( fileName );
222
223 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
224 return false;
225 }
226 else if( asset.asset_type == wxS( "spice" ) )
227 {
228 wxFileName spiceDir = baseDir;
229 spiceDir.AppendDir( prefix + wxS( "_spice" ) );
230
231 wxString fileName = SanitizeRemoteFileComponent(
232 asset.target_name.IsEmpty() ? asset.name : asset.target_name,
233 prefix + wxS( "_model.cir" ) );
234
235 if( !fileName.Lower().EndsWith( wxS( ".cir" ) ) )
236 fileName += wxS( ".cir" );
237
238 wxFileName outFile( spiceDir );
239 outFile.SetFullName( fileName );
240
241 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
242 return false;
243 }
244 }
245
246 // --- Symbols ---
247 for( size_t i : symbolIdx )
248 {
249 const REMOTE_PROVIDER_PART_ASSET& asset = aManifest.assets[i];
251
252 if( !downloadAsset( i, fetched ) )
253 return false;
254
255 wxFileName symbolDir = baseDir;
256 symbolDir.AppendDir( wxS( "symbols" ) );
257
258 const wxString libraryName = SanitizeRemoteFileComponent(
259 asset.target_library.IsEmpty() ? aContext.library_name : asset.target_library,
260 wxS( "symbols" ), true );
261 const wxString symbolName =
262 asset.target_name.IsEmpty() ? aContext.symbol_name : asset.target_name;
263 const wxString nickname = prefix + wxS( "_" ) + libraryName;
264
265 wxFileName outFile( symbolDir );
266 outFile.SetFullName( nickname + wxS( ".kicad_sym" ) );
267
268 if( !validateSymbolPayload( fetched.payload, symbolName, aError ) )
269 return false;
270
271 // Deserialize → mutate → save so the persisted symbol's Footprint field is
272 // already pointing at the local LIB_IDs of the bundle's footprints.
273 std::unique_ptr<LIB_SYMBOL> loaded =
274 LoadRemoteSymbolFromPayload( fetched.payload, symbolName, aError );
275
276 if( !loaded )
277 return false;
278
279 loaded->SetName( symbolName );
280 LIB_ID savedId;
281 savedId.SetLibNickname( nickname );
282 savedId.SetLibItemName( symbolName );
283 loaded->SetLibId( savedId );
284
285 ApplyFootprintLinks( *loaded, footprintLinks );
286
287 symbolDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
288
289 if( strictLibraryTables )
290 {
292 addToGlobal, true, aError ) )
293 return false;
294
296
297 if( !adapter
298 || adapter->SaveSymbol( nickname, loaded.get(), true )
300 {
301 aError = _( "Unable to save the downloaded symbol." );
302 return false;
303 }
304
305 (void) loaded.release(); // ownership transferred to library cache
306
308 libMgr.ReloadLibraryEntry( LIBRARY_TABLE_TYPE::SYMBOL, nickname, scope );
310 }
311 else
312 {
313 // Headless: write the raw payload to disk so the SCH plugin's library cache
314 // has an existing file to load, then re-save through the plugin to replace
315 // the symbol entry with the mutated (link-applied) copy. When there are no
316 // links to apply, the raw payload on disk is already correct and we skip
317 // the second save.
318 if( !WriteRemoteBinaryFile( outFile, fetched.payload, aError ) )
319 return false;
320
321 if( !footprintLinks.empty() )
322 {
323 try
324 {
325 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
326
327 if( !plugin )
328 {
329 aError = _( "Unable to access the KiCad symbol plugin." );
330 return false;
331 }
332
333 plugin->SaveSymbol( outFile.GetFullPath(), loaded.get() );
334 (void) loaded.release(); // ownership transferred to plugin's cache
335 }
336 catch( const IO_ERROR& e )
337 {
338 aError = wxString::Format( _( "Unable to save the downloaded symbol: %s" ),
339 e.What() );
340 return false;
341 }
342 }
343 }
344
345 importedSymbol = true;
346 placedNickname = nickname;
347 placedSymbolName = symbolName;
348 }
349
350 if( aPlaceSymbol )
351 {
352 if( !importedSymbol )
353 {
354 aError = _( "No symbol asset was available to place." );
355 return false;
356 }
357
358 if( !PlaceRemoteDownloadedSymbol( m_frame, placedNickname, placedSymbolName, aError ) )
359 return false;
360 }
361
362 return true;
363}
364
365
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)
std::optional< LIB_STATUS > LoadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname)
Synchronously loads the named library to LOADED state for the given type.
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
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:112
const wxString GetUniStringLibNickname() const
Definition lib_id.h:88
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:132
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...
SAVE_T SaveSymbol(const wxString &aNickname, const LIB_SYMBOL *aSymbol, bool aOverwrite=true)
Write aSymbol to an existing library given by aNickname.
#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 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