KiCad PCB EDA Suite
Loading...
Searching...
No Matches
footprint_library_adapter.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 * @author Jon Evans <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
22
23#include <env_vars.h>
24#include <footprint_info_impl.h>
25#include <thread_pool.h>
26#include <trace_helpers.h>
27#include <footprint.h>
28
29#include <magic_enum.hpp>
30#include <wx/hash.h>
31#include <wx/log.h>
32
33
34std::map<wxString, LIB_DATA> FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries;
35
37
38
43
44
46{
47 return ENV_VAR::GetVersionedEnvVarName( wxS( "FOOTPRINT_DIR" ) );
48}
49
50
52{
53 // TODO(JE) this shares most code with the symbol adapter
54 // TODO(JE) any reason to clean these up earlier?
55 std::erase_if( m_futures,
56 []( const std::future<void>& aFuture ) { return aFuture.valid(); } );
57
58 if( !m_futures.empty() )
59 {
60 wxLogTrace( traceLibraries, "FP: Cannot AsyncLoad, futures from a previous call remain!" );
61 return;
62 }
63
64 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( LIBRARY_TABLE_TYPE::FOOTPRINT );
65
66 m_loadTotal = rows.size();
67 m_loadCount.store( 0 );
68
69 if( m_loadTotal == 0 )
70 {
71 wxLogTrace( traceLibraries, "FP: AsyncLoad: no libraries left to load; exiting" );
72 return;
73 }
74
76
77 auto check =
78 []( const wxString& aLib, std::map<wxString, LIB_DATA>& aMap, std::mutex& aMutex )
79 {
80 std::lock_guard lock( aMutex );
81
82 if( aMap.contains( aLib ) )
83 {
84 if( aMap[aLib].status.load_status == LOAD_STATUS::LOADED )
85 return true;
86
87 aMap.erase( aLib );
88 }
89
90 return false;
91 };
92
93 for( const LIBRARY_TABLE_ROW* row : rows )
94 {
95 wxString nickname = row->Nickname();
96 LIBRARY_TABLE_SCOPE scope = row->Scope();
97
98 // TODO(JE) library tables -- check for modified global files
99 if( check( nickname, m_libraries, m_libraries_mutex ) )
100 {
101 --m_loadTotal;
102 continue;
103 }
104
105 if( check( nickname, GlobalLibraries, GlobalLibraryMutex ) )
106 {
107 --m_loadTotal;
108 continue;
109 }
110
111 m_futures.emplace_back( tp.submit_task(
112 [this, nickname, scope]()
113 {
114 if( m_abort.load() )
115 return;
116
117 LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( nickname );
118
119 if( result.has_value() )
120 {
121 LIB_DATA* lib = *result;
122 wxArrayString dummyList;
123 std::lock_guard lock ( lib->mutex );
124 lib->status.load_status = LOAD_STATUS::LOADING;
125
126 std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
127
128 try
129 {
130 pcbplugin( lib )->FootprintEnumerate( dummyList, getUri( lib->row ), false, &options );
131 wxLogTrace( traceLibraries, "FP: %s: library enumerated %zu items", nickname, dummyList.size() );
132 lib->status.load_status = LOAD_STATUS::LOADED;
133 }
134 catch( IO_ERROR& e )
135 {
136 lib->status.load_status = LOAD_STATUS::LOAD_ERROR;
137 lib->status.error = LIBRARY_ERROR( { e.What() } );
138 wxLogTrace( traceLibraries, "FP: %s: plugin threw exception: %s", nickname, e.What() );
139 }
140 }
141 else
142 {
143 switch( scope )
144 {
145 case LIBRARY_TABLE_SCOPE::GLOBAL:
146 {
147 std::lock_guard lock( GlobalLibraryMutex );
148
149 GlobalLibraries[nickname].status = LIB_STATUS( {
150 .load_status = LOAD_STATUS::LOAD_ERROR,
151 .error = result.error()
152 } );
153
154 break;
155 }
156
157 case LIBRARY_TABLE_SCOPE::PROJECT:
158 {
159 wxLogTrace( traceLibraries, "FP: project library error: %s: %s", nickname, result.error().message );
160 std::lock_guard lock( m_libraries_mutex );
161
162 m_libraries[nickname].status = LIB_STATUS( {
163 .load_status = LOAD_STATUS::LOAD_ERROR,
164 .error = result.error()
165 } );
166
167 break;
168 }
169
170 default:
171 wxFAIL_MSG( "Unexpected library table scope" );
172 }
173 }
174
175 ++m_loadCount;
176 }, BS::pr::lowest ) );
177 }
178
179 wxLogTrace( traceLibraries, "FP: Started async load of %zu libraries", m_loadTotal );
180}
181
182
183std::optional<LIB_STATUS> FOOTPRINT_LIBRARY_ADAPTER::LoadOne( const wxString& aNickname )
184{
185 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
186 {
187 LIB_DATA* lib = *result;
188 std::lock_guard lock ( lib->mutex );
190
191 std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
192
193 try
194 {
195 wxArrayString dummyList;
196 pcbplugin( lib )->FootprintEnumerate( dummyList, getUri( lib->row ), true, &options );
197 wxLogTrace( traceLibraries, "Sym: %s: library enumerated %zu items", aNickname, dummyList.size() );
199 }
200 catch( IO_ERROR& e )
201 {
203 lib->status.error = LIBRARY_ERROR( { e.What() } );
204 wxLogTrace( traceLibraries, "Sym: %s: plugin threw exception: %s", aNickname, e.What() );
205 }
206
207 return lib->status;
208 }
209
210 return std::nullopt;
211}
212
213
214std::optional<LIB_STATUS> FOOTPRINT_LIBRARY_ADAPTER::GetLibraryStatus( const wxString& aNickname ) const
215{
216 // TODO(JE) this could be deduplicated with virtual access to GlobalLibraries
217 if( m_libraries.contains( aNickname ) )
218 return m_libraries.at( aNickname ).status;
219
220 if( GlobalLibraries.contains( aNickname ) )
221 return GlobalLibraries.at( aNickname ).status;
222
223 return std::nullopt;
224}
225
226
227std::vector<FOOTPRINT*> FOOTPRINT_LIBRARY_ADAPTER::GetFootprints( const wxString& aNickname, bool aBestEfforts )
228{
229 std::vector<FOOTPRINT*> footprints;
230
231 std::optional<const LIB_DATA*> maybeLib = fetchIfLoaded( aNickname );
232
233 if( !maybeLib )
234 return footprints;
235
236 const LIB_DATA* lib = *maybeLib;
237 std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
238
239 wxArrayString namesAS;
240
241 try
242 {
243 pcbplugin( lib )->FootprintEnumerate( namesAS, getUri( lib->row ), true, &options );
244 }
245 catch( IO_ERROR& e )
246 {
247 wxLogTrace( traceLibraries, "FP: Exception enumerating library %s: %s",
248 lib->row->Nickname(), e.What() );
249 }
250
251 for( const wxString& footprintName : namesAS )
252 {
253 FOOTPRINT* footprint = nullptr;
254
255 try
256 {
257 footprint = pcbplugin( lib )->FootprintLoad( getUri( lib->row ),footprintName, false, &options );
258 }
259 catch( IO_ERROR& e )
260 {
261 wxLogTrace( traceLibraries, "Sym: Exception enumerating library %s: %s",
262 lib->row->Nickname(), e.What() );
263 continue;
264 }
265
266 wxCHECK2( footprint, continue );
267
268 LIB_ID id = footprint->GetFPID();
269 id.SetLibNickname( lib->row->Nickname() );
270 footprint->SetFPID( id );
271 footprints.emplace_back( footprint );
272 }
273
274 return footprints;
275}
276
277
278std::vector<wxString> FOOTPRINT_LIBRARY_ADAPTER::GetFootprintNames( const wxString& aNickname, bool aBestEfforts )
279{
280 // TODO(JE) can we kill wxArrayString in internal API?
281 wxArrayString namesAS;
282 std::vector<wxString> names;
283
284 if( std::optional<const LIB_DATA*> maybeLib = fetchIfLoaded( aNickname ) )
285 {
286 const LIB_DATA* lib = *maybeLib;
287 std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
288
289 try
290 {
291 pcbplugin( lib )->FootprintEnumerate( namesAS, getUri( lib->row ), true, &options );
292 }
293 catch( IO_ERROR& e )
294 {
295 wxLogTrace( traceLibraries, "FP: Exception enumerating library %s: %s",
296 lib->row->Nickname(), e.What() );
297 }
298 }
299
300 for( const wxString& name : namesAS )
301 names.emplace_back( name );
302
303 return names;
304}
305
306
307long long FOOTPRINT_LIBRARY_ADAPTER::GenerateTimestamp( const wxString* aNickname )
308{
309 long long hash = 0;
310
311 if( aNickname )
312 {
313 wxCHECK( HasLibrary( *aNickname, true ), hash );
314
315 if( std::optional<const LIB_DATA*> r = fetchIfLoaded( *aNickname ); r.has_value() )
316 {
317 PCB_IO* plugin = dynamic_cast<PCB_IO*>( ( *r )->plugin.get() );
318 wxCHECK( plugin, hash );
319 return plugin->GetLibraryTimestamp( LIBRARY_MANAGER::GetFullURI( ( *r )->row, true ) ) +
320 wxHashTable::MakeKey( *aNickname );
321 }
322 }
323
324 for( const wxString& nickname : GetLibraryNames() )
325 {
326 if( std::optional<const LIB_DATA*> r = fetchIfLoaded( nickname ); r.has_value() )
327 {
328 PCB_IO* plugin = dynamic_cast<PCB_IO*>( ( *r )->plugin.get() );
329 wxCHECK2( plugin, continue );
330 hash += plugin->GetLibraryTimestamp( LIBRARY_MANAGER::GetFullURI( ( *r )->row, true ) ) +
331 wxHashTable::MakeKey( nickname );
332 }
333 }
334
335 return hash;
336}
337
338
339bool FOOTPRINT_LIBRARY_ADAPTER::FootprintExists( const wxString& aNickname, const wxString& aName )
340{
341 if( std::optional<const LIB_DATA*> maybeLib = fetchIfLoaded( aNickname ) )
342 {
343 const LIB_DATA* lib = *maybeLib;
344 std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
345
346 return pcbplugin( lib )->FootprintExists( getUri( lib->row ), aName, &options );
347 }
348
349 return false;
350}
351
352
353FOOTPRINT* FOOTPRINT_LIBRARY_ADAPTER::LoadFootprint( const wxString& aNickname, const wxString& aName, bool aKeepUUID )
354{
355 if( std::optional<const LIB_DATA*> lib = fetchIfLoaded( aNickname ) )
356 {
357 if( FOOTPRINT* footprint = pcbplugin( *lib )->FootprintLoad( getUri( ( *lib )->row ), aName ) )
358 {
359 LIB_ID id = footprint->GetFPID();
360 id.SetLibNickname( ( *lib )->row->Nickname() );
361 footprint->SetFPID( id );
362 return footprint;
363 }
364 }
365 else
366 {
367 wxLogTrace( traceLibraries, "LoadFootprint: requested library %s not loaded", aNickname );
368 }
369
370 return nullptr;
371}
372
373
375{
376 wxString nickname = aFootprintId.GetLibNickname();
377 wxString footprintName = aFootprintId.GetLibItemName();
378
379 if( nickname.size() )
380 return LoadFootprint( nickname, footprintName, aKeepUUID );
381
382 // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match:
383 for( const wxString& library : GetLibraryNames() )
384 {
385 // FootprintLoad() returns NULL on not found, does not throw exception
386 // unless there's an IO_ERROR.
387 if( FOOTPRINT* ret = LoadFootprint( library, footprintName, aKeepUUID ) )
388 return ret;
389 }
390
391 return nullptr;
392}
393
394
396 const FOOTPRINT* aFootprint,
397 bool aOverwrite )
398{
399 wxCHECK( aFootprint, SAVE_SKIPPED );
400
401 if( std::optional<const LIB_DATA*> lib = fetchIfLoaded( aNickname ) )
402 {
403 if( !aOverwrite )
404 {
405 wxString fpname = aFootprint->GetFPID().GetLibItemName();
406
407 try
408 {
409 FOOTPRINT* existing = pcbplugin( *lib )->FootprintLoad( getUri( ( *lib )->row ), fpname, false );
410
411 if( existing )
412 return SAVE_SKIPPED;
413 }
414 catch( IO_ERROR& e )
415 {
416 wxLogTrace( traceLibraries, "SaveFootprint: error checking for existing footprint %s: %s",
417 aFootprint->GetFPIDAsString(), e.What() );
418 return SAVE_SKIPPED;
419 }
420 }
421
422 try
423 {
424 pcbplugin( *lib )->FootprintSave( getUri( ( *lib )->row ), aFootprint );
425 }
426 catch( IO_ERROR& e )
427 {
428 wxLogTrace( traceLibraries, "SaveFootprint: error saving %s: %s",
429 aFootprint->GetFPIDAsString(), e.What() );
430 return SAVE_SKIPPED;
431 }
432
433 return SAVE_OK;
434 }
435 else
436 {
437 wxLogTrace( traceLibraries, "SaveFootprint: requested library %s not loaded", aNickname );
438 return SAVE_SKIPPED;
439 }
440}
441
442void FOOTPRINT_LIBRARY_ADAPTER::DeleteFootprint( const wxString& aNickname, const wxString& aFootprintName )
443{
444 if( std::optional<const LIB_DATA*> lib = fetchIfLoaded( aNickname ) )
445 {
446 try
447 {
448 pcbplugin( *lib )->FootprintDelete( getUri( ( *lib )->row ), aFootprintName );
449 }
450 catch( IO_ERROR& e )
451 {
452 wxLogTrace( traceLibraries, "DeleteFootprint: error deleting %s:%s: %s", aNickname,
453 aFootprintName, e.What() );
454 }
455 }
456 else
457 {
458 wxLogTrace( traceLibraries, "DeleteFootprint: requested library %s not loaded", aNickname );
459 }
460}
461
462
464{
465 if( m_libraries.contains( aLib ) )
466 return m_libraries[aLib].plugin->IsLibraryWritable( getUri( m_libraries[aLib].row ) );
467
468 if( GlobalLibraries.contains( aLib ) )
469 return GlobalLibraries[aLib].plugin->IsLibraryWritable( getUri( GlobalLibraries[aLib].row ) );
470
471 return false;
472}
473
474
475std::optional<LIBRARY_ERROR> FOOTPRINT_LIBRARY_ADAPTER::LibraryError( const wxString& aNickname ) const
476{
477 if( m_libraries.contains( aNickname ) )
478 {
479 return m_libraries.at( aNickname ).status.error;
480 }
481
482 if( GlobalLibraries.contains( aNickname ) )
483 {
484 return GlobalLibraries.at( aNickname ).status.error;
485 }
486
487 return std::nullopt;
488}
489
490
492{
494
495 if( type == PCB_IO_MGR::PCB_FILE_UNKNOWN )
496 {
497 wxLogTrace( traceLibraries, "FP: Plugin type %s is unknown!", row->Type() );
498 wxString msg = wxString::Format( _( "Unknown library type %s " ), row->Type() );
499 return tl::unexpected( LIBRARY_ERROR( msg ) );
500 }
501
503 wxCHECK( plugin, tl::unexpected( LIBRARY_ERROR( _( "Internal error" ) ) ) );
504
505 wxLogTrace( traceLibraries, "FP: Library %s (%s) plugin created",
506 row->Nickname(), magic_enum::enum_name( row->Scope() ) );
507
508 return plugin;
509}
510
511
513{
514 PCB_IO* ret = dynamic_cast<PCB_IO*>( aRow->plugin.get() );
515 wxCHECK( aRow->plugin && ret, nullptr );
516 return ret;
517}
const char * name
void AsyncLoad() override
Loads all available libraries for this adapter type in the background.
bool IsFootprintLibWritable(const wxString &aNickname)
Return true if the library given by aNickname is writable.
void DeleteFootprint(const wxString &aNickname, const wxString &aFootprintName)
Deletes the aFootprintName from the library given by aNickname.
std::vector< FOOTPRINT * > GetFootprints(const wxString &aNickname, bool aBestEfforts=false)
Retrieves a list of footprints contained in a given loaded library.
std::optional< LIB_STATUS > GetLibraryStatus(const wxString &aNickname) const override
Returns the status of a loaded library, or nullopt if the library hasn't been loaded (yet)
IO_BASE * plugin(const LIB_DATA *aRow) override
SAVE_T SaveFootprint(const wxString &aNickname, const FOOTPRINT *aFootprint, bool aOverwrite=true)
Write aFootprint to an existing library given by aNickname.
std::vector< wxString > GetFootprintNames(const wxString &aNickname, bool aBestEfforts=false)
Retrieves a list of footprint names contained in a given loaded library.
FOOTPRINT * LoadFootprint(const wxString &aNickname, const wxString &aName, bool aKeepUUID)
Load a FOOTPRINT having aName from the library given by aNickname.
static PCB_IO * pcbplugin(const LIB_DATA *aRow)
Helper to cast the ABC plugin in the LIB_DATA* to a concrete plugin.
FOOTPRINT * LoadFootprintWithOptionalNickname(const LIB_ID &aFootprintId, bool aKeepUUID)
Load a footprint having aFootprintId with possibly an empty nickname.
LIBRARY_RESULT< IO_BASE * > createPlugin(const LIBRARY_TABLE_ROW *row) override
Creates a concrete plugin for the given row.
std::optional< LIB_STATUS > LoadOne(const wxString &aNickname)
Loads or reloads the given library, if it exists.
std::optional< LIBRARY_ERROR > LibraryError(const wxString &aNickname) const override
static std::map< wxString, LIB_DATA > GlobalLibraries
FOOTPRINT_LIBRARY_ADAPTER(LIBRARY_MANAGER &aManager)
long long GenerateTimestamp(const wxString *aNickname)
Generates a filesystem timestamp / hash value for library(ies)
SAVE_T
The set of return values from SaveSymbol() below.
bool FootprintExists(const wxString &aNickname, const wxString &aName)
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:270
wxString GetFPIDAsString() const
Definition footprint.h:275
const LIB_ID & GetFPID() const
Definition footprint.h:269
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()
LIBRARY_MANAGER_ADAPTER(LIBRARY_MANAGER &aManager)
Constructs a type-specific adapter into the library manager.
LIBRARY_RESULT< LIB_DATA * > loadIfNeeded(const wxString &aNickname)
Fetches a loaded library, triggering a load of that library if it isn't loaded yet.
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::map< wxString, LIB_DATA > m_libraries
std::vector< wxString > GetLibraryNames() const
Returns a list of library nicknames that are available (skips any that failed to load)
std::vector< std::future< void > > m_futures
static wxString getUri(const LIBRARY_TABLE_ROW *aRow)
std::atomic< size_t > m_loadCount
LIBRARY_MANAGER & m_manager
std::optional< const LIB_DATA * > fetchIfLoaded(const wxString &aNickname) const
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
LIBRARY_TABLE_SCOPE Scope() const
std::map< std::string, UTF8 > GetOptionsMap() const
const wxString & Type() const
const wxString & Nickname() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:100
const UTF8 & GetLibItemName() const
Definition lib_id.h:102
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:87
static PCB_FILE_T EnumFromStr(const wxString &aFileType)
Return the PCB_FILE_T from the corresponding plugin type name: "kicad", "legacy", etc.
PCB_FILE_T
The set of file types that the PCB_IO_MGR knows about, and for which there has been a plugin written,...
Definition pcb_io_mgr.h:56
@ PCB_FILE_UNKNOWN
0 is not a legal menu id on Mac
Definition pcb_io_mgr.h:57
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
A base class that BOARD loading and saving plugins should derive from.
Definition pcb_io.h:71
virtual void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr)
Return a list of footprint names contained within the library at aLibraryPath.
Definition pcb_io.cpp:95
virtual bool FootprintExists(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr)
Check for the existence of a footprint.
Definition pcb_io.cpp:136
virtual void FootprintDelete(const wxString &aLibraryPath, const wxString &aFootprintName, const std::map< std::string, UTF8 > *aProperties=nullptr)
Delete aFootprintName from the library at aLibraryPath.
Definition pcb_io.cpp:160
virtual FOOTPRINT * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, bool aKeepUUID=false, const std::map< std::string, UTF8 > *aProperties=nullptr)
Load a footprint having aFootprintName from the aLibraryPath containing a library format that this PC...
Definition pcb_io.cpp:144
virtual void FootprintSave(const wxString &aLibraryPath, const FOOTPRINT *aFootprint, const std::map< std::string, UTF8 > *aProperties=nullptr)
Write aFootprint to an existing library located at aLibraryPath.
Definition pcb_io.cpp:152
#define _(s)
Functions related to environment variables, including help functions.
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
tl::expected< ResultType, LIBRARY_ERROR > LIBRARY_RESULT
LIBRARY_TABLE_SCOPE
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
Storage for an actual loaded library (including library content owned by the plugin)
LIB_STATUS status
std::unique_ptr< IO_BASE > plugin
std::mutex mutex
const LIBRARY_TABLE_ROW * row
std::optional< LIBRARY_ERROR > error
LOAD_STATUS load_status
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
wxLogTrace helper definitions.