KiCad PCB EDA Suite
Loading...
Searching...
No Matches
library_manager.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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <chrono>
22#include <common.h>
23#include <env_vars.h>
24#include <list>
25#include <magic_enum.hpp>
26#include <thread_pool.h>
27#include <ranges>
28#include <set>
29#include <unordered_set>
30
31#include <paths.h>
32#include <pgm_base.h>
33#include <richio.h>
34#include <string_utils.h>
35#include <trace_helpers.h>
37
39
40using namespace std::chrono_literals;
44#include <wx/dir.h>
45#include <wx/log.h>
46
48{
49 std::vector<LIBRARY_TABLE> tables;
50};
51
52
53std::recursive_mutex& LIBRARY_MANAGER_ADAPTER::pluginMutex( const wxString& aNickname )
54{
55 // Leaked and never erased: must outlive every caller including static teardown, and stable node
56 // addresses keep the returned reference valid.
57 static std::mutex& registryLock = *new std::mutex;
58 static std::map<wxString, std::unique_ptr<std::recursive_mutex>>& registry =
59 *new std::map<wxString, std::unique_ptr<std::recursive_mutex>>;
60
61 std::lock_guard guard( registryLock );
62 std::unique_ptr<std::recursive_mutex>& slot = registry[aNickname];
63
64 if( !slot )
65 slot = std::make_unique<std::recursive_mutex>();
66
67 return *slot;
68}
69
70
71LIBRARY_MANAGER::LIBRARY_MANAGER( const PROJECT* aProject ) : m_project( aProject )
72{
73}
74
75
77{
78 // Adapters join plugin worker threads that still query the row cache, so they must die before it
79 std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_MANAGER_ADAPTER>> adapters;
80
81 {
82 std::scoped_lock lock( m_adaptersMutex );
83 adapters.swap( m_adapters );
84 }
85}
86
87
89{
91}
92
93
94void LIBRARY_MANAGER::loadTables( const wxString& aTablePath, LIBRARY_TABLE_SCOPE aScope,
95 std::vector<LIBRARY_TABLE_TYPE> aTablesToLoad )
96{
97 {
98 std::lock_guard lock( m_rowCacheMutex );
99 m_rowCache.clear();
100 }
101
102 auto getTarget =
103 [&]() -> std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>&
104 {
105 switch( aScope )
106 {
108 return m_tables;
109
111 return m_projectTables;
112
113 default:
114 wxCHECK_MSG( false, m_tables, "Invalid scope passed to loadTables" );
115 }
116 };
117
118 std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>& aTarget = getTarget();
119
120 if( aTablesToLoad.size() == 0 )
122
123 for( LIBRARY_TABLE_TYPE type : aTablesToLoad )
124 {
125 aTarget.erase( type );
126
127 wxFileName fn( aTablePath, tableFileName( type ) );
128
129 if( fn.IsFileReadable() )
130 {
131 std::unique_ptr<LIBRARY_TABLE> table = std::make_unique<LIBRARY_TABLE>( fn, aScope );
132
133 if( table->Type() != type )
134 {
135 auto actualName = magic_enum::enum_name( table->Type() );
136 auto expectedName = magic_enum::enum_name( type );
137 wxLogWarning( wxS( "Library table '%s' has type %s but expected %s; skipping" ),
138 fn.GetFullPath(),
139 wxString( actualName.data(), actualName.size() ),
140 wxString( expectedName.data(), expectedName.size() ) );
141 continue;
142 }
143
144 aTarget[type] = std::move( table );
145 loadNestedTables( *aTarget[type] );
146 }
147 else
148 {
149 wxLogTrace( traceLibraries, "No library table found at %s", fn.GetFullPath() );
150 }
151 }
152}
153
154
156{
157 std::unordered_set<wxString> seenTables;
158
159 std::function<void( LIBRARY_TABLE& )> processOneTable =
160 [&]( LIBRARY_TABLE& aTable )
161 {
162 seenTables.insert( aTable.Path() );
163
164 if( !aTable.IsOk() )
165 return;
166
167 for( LIBRARY_TABLE_ROW& row : aTable.Rows() )
168 {
169 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
170 {
171 wxFileName file( ExpandEnvVarSubstitutions( row.URI(), &Project() ) );
172
173 // URI may be relative to parent
174 file.MakeAbsolute( wxFileName( aTable.Path() ).GetPath() );
175
177 wxString src = file.GetFullPath();
178
179 if( seenTables.contains( src ) )
180 {
181 wxLogTrace( traceLibraries, "Library table %s has already been loaded!", src );
182 row.SetOk( false );
183 row.SetErrorDescription( _( "A reference to this library table already exists" ) );
184 continue;
185 }
186
187 auto child = std::make_unique<LIBRARY_TABLE>( file, aRootTable.Scope() );
188
189 processOneTable( *child );
190
191 if( !child->IsOk() )
192 {
193 row.SetOk( false );
194 row.SetErrorDescription( child->ErrorDescription() );
195 }
196
197 applyLibOverrides( *child );
198
199 m_childTables.insert_or_assign( row.URI(), std::move( child ) );
200 }
201 }
202 };
203
204 processOneTable( aRootTable );
205}
206
207
209{
210 if( !aTable.IsReadOnly() )
211 return;
212
214 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
215
216 if( !settings )
217 return;
218
219 wxString tablePath = aTable.Path();
220
221 auto it = settings->m_LibOverrides.find( tablePath );
222
223 if( it == settings->m_LibOverrides.end() )
224 return;
225
226 const std::map<wxString, LIB_OVERRIDE>& overrides = it->second;
227
228 for( LIBRARY_TABLE_ROW& row : aTable.Rows() )
229 {
230 auto overIt = overrides.find( row.Nickname() );
231
232 if( overIt != overrides.end() )
233 {
234 row.SetDisabled( overIt->second.disabled );
235 row.SetHidden( overIt->second.hidden );
236 }
237 }
238}
239
240
245
246
247void LIBRARY_MANAGER::SetLibOverride( const wxString& aTablePath, const wxString& aNickname,
248 bool aDisabled, bool aHidden )
249{
251 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
252
253 wxCHECK( settings, /* void */ );
254
255 if( !aDisabled && !aHidden )
256 {
257 ClearLibOverride( aTablePath, aNickname );
258 return;
259 }
260
261 settings->m_LibOverrides[aTablePath][aNickname] = { aDisabled, aHidden };
262}
263
264
265void LIBRARY_MANAGER::ClearLibOverride( const wxString& aTablePath, const wxString& aNickname )
266{
268 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
269
270 wxCHECK( settings, /* void */ );
271
272 auto tableIt = settings->m_LibOverrides.find( aTablePath );
273
274 if( tableIt == settings->m_LibOverrides.end() )
275 return;
276
277 tableIt->second.erase( aNickname );
278
279 if( tableIt->second.empty() )
280 settings->m_LibOverrides.erase( tableIt );
281}
282
283
285{
286 switch( aType )
287 {
291 default: wxCHECK( false, wxEmptyString );
292 }
293}
294
295
297{
298 if( aScope == LIBRARY_TABLE_SCOPE::GLOBAL )
299 {
300 wxCHECK( !m_tables.contains( aType ), /* void */ );
301 wxFileName fn( PATHS::GetUserSettingsPath(), tableFileName( aType ) );
302
303 m_tables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::GLOBAL );
304 m_tables[aType]->SetType( aType );
305 }
306 else if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
307 {
308 wxCHECK( !m_projectTables.contains( aType ), /* void */ );
309 wxFileName fn( Project().GetProjectDirectory(), tableFileName( aType ) );
310
311 m_projectTables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::PROJECT );
312 m_projectTables[aType]->SetType( aType );
313 m_projectTables[aType]->SetOk();
314 }
315}
316
317
318class PCM_LIB_TRAVERSER final : public wxDirTraverser
319{
320public:
321 explicit PCM_LIB_TRAVERSER( const wxString& aBasePath, LIBRARY_MANAGER& aManager,
322 const wxString& aPrefix ) :
323 m_manager( aManager ),
324 m_project( Pgm().GetSettingsManager().Prj() ),
325 m_path_prefix( aBasePath ),
326 m_lib_prefix( aPrefix )
327 {
328 wxFileName f( aBasePath, "" );
329 m_prefix_dir_count = f.GetDirCount();
330
334 .value_or( nullptr );
335 }
336
338 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
339 {
340 wxFileName file = wxFileName::FileName( aFilePath );
341
342 // consider a file to be a lib if it's name ends with .kicad_sym and
343 // it is under $KICADn_3RD_PARTY/symbols/<pkgid>/ i.e. has nested level of at least +2
344 if( file.GetExt() == wxT( "kicad_sym" )
345 && file.GetDirCount() >= m_prefix_dir_count + 2
346 && file.GetDirs()[m_prefix_dir_count] == wxT( "symbols" ) )
347 {
349 }
350
351 return wxDIR_CONTINUE;
352 }
353
355 wxDirTraverseResult OnDir( const wxString& dirPath ) override
356 {
357 static wxString designBlockExt = wxString::Format( wxS( ".%s" ), FILEEXT::KiCadDesignBlockLibPathExtension );
358 wxFileName dir = wxFileName::DirName( dirPath );
359
360 // consider a directory to be a lib if it's name ends with .pretty and
361 // it is under $KICADn_3RD_PARTY/footprints/<pkgid>/ i.e. has nested level of at least +3
362 if( dirPath.EndsWith( wxS( ".pretty" ) )
363 && dir.GetDirCount() >= m_prefix_dir_count + 3
364 && dir.GetDirs()[m_prefix_dir_count] == wxT( "footprints" ) )
365 {
367 }
368 else if( dirPath.EndsWith( designBlockExt )
369 && dir.GetDirCount() >= m_prefix_dir_count + 3
370 && dir.GetDirs()[m_prefix_dir_count] == wxT( "design_blocks" ) )
371 {
372 addRowIfNecessary( m_designBlockTable, dir, ADD_MODE::AM_DIRECTORY, designBlockExt.Len() );
373 }
374
375 return wxDIR_CONTINUE;
376 }
377
378 std::set<LIBRARY_TABLE*> Modified() const { return m_modified; }
379
380private:
381 void ensureUnique( LIBRARY_TABLE* aTable, const wxString& aBaseName, wxString& aNickname ) const
382 {
383 if( aTable->HasRow( aNickname ) )
384 {
385 int increment = 1;
386
387 do
388 {
389 aNickname = wxString::Format( "%s%s_%d", m_lib_prefix, aBaseName, increment );
390 increment++;
391 } while( aTable->HasRow( aNickname ) );
392 }
393 }
394
395 enum class ADD_MODE
396 {
399 };
400
401 void addRowIfNecessary( LIBRARY_TABLE* aTable, const wxFileName& aSource, ADD_MODE aMode,
402 int aExtensionLength )
403 {
404 // No global table file yet (first run): nothing to auto-add.
405 if( !aTable )
406 return;
407
408 wxString versionedPath = wxString::Format( wxS( "${%s}" ),
409 ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
410
411 wxArrayString parts = aSource.GetDirs();
412 parts.RemoveAt( 0, m_prefix_dir_count );
413 parts.Insert( versionedPath, 0 );
414
415 if( aMode == ADD_MODE::AM_FILE )
416 parts.Add( aSource.GetFullName() );
417
418 wxString libPath = wxJoin( parts, '/' );
419
420 if( !aTable->HasRowWithURI( libPath, m_project ) )
421 {
422 wxString name = parts.Last().substr( 0, parts.Last().length() - aExtensionLength );
423 wxString nickname = wxString::Format( "%s%s", m_lib_prefix, name );
424
425 ensureUnique( aTable, name, nickname );
426
427 wxLogTrace( traceLibraries, "Manager: Adding PCM lib '%s' as '%s'", libPath, nickname );
428
429 LIBRARY_TABLE_ROW& row = aTable->InsertRow();
430
431 row.SetNickname( nickname );
432 row.SetURI( libPath );
433 row.SetType( wxT( "KiCad" ) );
434 row.SetDescription( _( "Added by Plugin and Content Manager" ) );
435 m_modified.insert( aTable );
436 }
437 else
438 {
439 wxLogTrace( traceLibraries, "Manager: Not adding existing PCM lib '%s'", libPath );
440 }
441 }
442
443private:
447 wxString m_lib_prefix;
449 std::set<LIBRARY_TABLE*> m_modified;
450
454};
455
456
458{
459 wxString basePath = PATHS::GetUserSettingsPath();
460
461 wxFileName fn( basePath, tableFileName( aType ) );
462 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
463
464 return fn.GetFullPath();
465}
466
467
469{
470 wxString basePath = PATHS::GetStockTemplatesPath();
471
472 wxFileName fn( basePath, tableFileName( aType ) );
473 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
474
475 return fn.GetFullPath();
476}
477
478
480{
481 wxString templateDirVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
482
483 return wxString::Format( wxS( "${%s}/%s" ), templateDirVar, tableFileName( aType ) );
484}
485
486
488{
489 const wxString templateDirVar = ENV_VAR::GetVersionedEnvVarName( wxS( "TEMPLATE_DIR" ) );
490
491 // Only relocatable installs (AppImage, Nix) export the template-dir variable so the stock
492 // path follows the remounted prefix. There we store the unresolved token, which re-resolves
493 // each launch instead of dangling. A standard install leaves the variable at its built-in
494 // default, so we keep the historical resolved absolute path.
495 if( COMMON_SETTINGS* common = Pgm().GetCommonSettings() )
496 {
497 auto it = common->m_Env.vars.find( templateDirVar );
498
499 if( it != common->m_Env.vars.end() && it->second.GetDefinedExternally() )
500 return StockTableTokenizedURI( aType );
501 }
502
503 return StockTablePath( aType );
504}
505
506
507bool LIBRARY_MANAGER::IsTableValid( const wxString& aPath )
508{
509 if( wxFileName fn( aPath ); fn.IsFileReadable() )
510 {
512
513 if( temp.IsOk() )
514 return true;
515 }
516
517 return false;
518}
519
520
522{
523 return InvalidGlobalTables().empty();
524}
525
526
527std::vector<LIBRARY_TABLE_TYPE> LIBRARY_MANAGER::InvalidGlobalTables()
528{
529 std::vector<LIBRARY_TABLE_TYPE> invalidTables;
530 wxString basePath = PATHS::GetUserSettingsPath();
531
535 {
536 wxFileName fn( basePath, tableFileName( tableType ) );
537
538 if( !IsTableValid( fn.GetFullPath() ) )
539 invalidTables.emplace_back( tableType );
540 }
541
542 return invalidTables;
543}
544
545
546bool LIBRARY_MANAGER::CreateGlobalTable( LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries )
547{
548 wxFileName fn( DefaultGlobalTablePath( aType ) );
549
551 table.SetType( aType );
552 table.Rows().clear();
553
554 wxFileName defaultLib( StockTablePath( aType ) );
555
556 if( aPopulateDefaultLibraries && defaultLib.IsFileReadable() )
557 {
558 LIBRARY_TABLE_ROW& chained = table.InsertRow();
560 chained.SetNickname( wxT( "KiCad" ) );
561 chained.SetDescription( _( "KiCad Default Libraries" ) );
562 chained.SetURI( StockTableReferenceURI( aType ) );
563 }
564 else if( aPopulateDefaultLibraries )
565 {
566 auto typeName = magic_enum::enum_name( aType );
567 wxLogTrace( traceLibraries, "No stock library table for %s at '%s'; creating empty global table",
568 wxString( typeName.data(), typeName.size() ), defaultLib.GetFullPath() );
569 }
570
571 try
572 {
573 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( fn.GetFullPath(), KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
574 table.Format( &formatter );
575 formatter.Finish();
576 }
577 catch( IO_ERROR& e )
578 {
579 wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() );
580 return false;
581 }
582
583 return true;
584}
585
586
587void LIBRARY_MANAGER::LoadGlobalTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
588{
589 // Cancel any in-progress load
590 {
591 std::scoped_lock lock( m_adaptersMutex );
592
593 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
594 adapter->GlobalTablesChanged( aTablesToLoad );
595 }
596
598
599 // A passive load must not maintain or save the active user's library tables.
600 if( IsProjectScoped() )
601 return;
602
604 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
605
606 wxCHECK( settings, /* void */ );
607
608 const ENV_VAR_MAP& vars = Pgm().GetLocalEnvVariables();
609 std::optional<wxString> packagesPath = ENV_VAR::GetVersionedEnvVarValue( vars, wxT( "3RD_PARTY" ) );
610
611 if( packagesPath && settings->m_PcmLibAutoAdd )
612 {
613 // Scan for libraries in PCM packages directory
614 wxFileName d( *packagesPath, "" );
615
616 if( d.DirExists() )
617 {
618 PCM_LIB_TRAVERSER traverser( *packagesPath, *this, settings->m_PcmLibPrefix );
619 wxDir dir( d.GetPath() );
620
621 dir.Traverse( traverser );
622
623 for( LIBRARY_TABLE* table : traverser.Modified() )
624 {
625 table->Save().map_error(
626 []( const LIBRARY_ERROR& aError )
627 {
628 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-add: %s" ),
629 aError.message );
630 } );
631 }
632 }
633 }
634
635 auto cleanupRemovedPCMLibraries =
636 [&]( LIBRARY_TABLE_TYPE aType )
637 {
638 LIBRARY_TABLE* table = Table( aType, LIBRARY_TABLE_SCOPE::GLOBAL ).value_or( nullptr );
639 // No global table file yet (first run): nothing to scan for PCM removals.
640 if( !table )
641 return;
642
643 auto toErase = std::ranges::remove_if( table->Rows(),
644 [&]( const LIBRARY_TABLE_ROW& aRow )
645 {
646 if( !IsPcmManagedRow( aRow ) )
647 return false;
648
649 wxString path = GetFullURI( &aRow, true );
650 return !wxFileName::Exists( path );
651 } );
652
653 bool hadRemovals = !toErase.empty();
654 table->Rows().erase( toErase.begin(), toErase.end() );
655
656 if( hadRemovals )
657 {
658 table->Save().map_error(
659 []( const LIBRARY_ERROR& aError )
660 {
661 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-remove: %s" ),
662 aError.message );
663 } );
664 }
665 };
666
667 if( packagesPath && settings->m_PcmLibAutoRemove )
668 {
669 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::SYMBOL );
670 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::FOOTPRINT );
671 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::DESIGN_BLOCK );
672 }
673}
674
675
676void LIBRARY_MANAGER::LoadProjectTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
677{
678 LoadProjectTables( Project().GetProjectDirectory(), aTablesToLoad );
679}
680
681
683{
684 // Abort any running async library loads before reloading project tables.
685 // Background workers hold raw LIBRARY_TABLE_ROW pointers that become dangling
686 // when loadTables() destroys and replaces the table objects.
688
689 LoadProjectTables( Project().GetProjectDirectory() );
690
691 std::scoped_lock lock( m_adaptersMutex );
692
693 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
694 adapter->ProjectChanged();
695}
696
697
699{
700 std::scoped_lock lock( m_adaptersMutex );
701
702 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
703 adapter->AbortAsyncLoad();
704}
705
706
708 std::unique_ptr<LIBRARY_MANAGER_ADAPTER>&& aAdapter )
709{
710 std::scoped_lock lock( m_adaptersMutex );
711
712 wxCHECK_MSG( !m_adapters.contains( aType ), /**/, "You should only register an adapter once!" );
713
714 m_adapters[aType] = std::move( aAdapter );
715}
716
717
719{
720 std::unique_ptr<LIBRARY_MANAGER_ADAPTER> retired;
721 std::scoped_lock lock( m_adaptersMutex );
722 if( !m_adapters.contains( aType ) )
723 return false;
724
725 if( m_adapters[aType].get() != aAdapter )
726 return false;
727
728 // The adapter joins plugin worker threads, so it must not be destroyed under the adapters mutex
729 retired = std::move( m_adapters[aType] );
730 m_adapters.erase( aType );
731 return true;
732}
733
734
735std::optional<LIBRARY_MANAGER_ADAPTER*> LIBRARY_MANAGER::Adapter( LIBRARY_TABLE_TYPE aType ) const
736{
737 std::scoped_lock lock( m_adaptersMutex );
738
739 if( m_adapters.contains( aType ) )
740 return m_adapters.at( aType ).get();
741
742 return std::nullopt;
743}
744
745
746std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER::Table( LIBRARY_TABLE_TYPE aType,
747 LIBRARY_TABLE_SCOPE aScope )
748{
749 switch( aScope )
750 {
753 wxCHECK_MSG( false, std::nullopt, "Table() requires a single scope" );
754
756 {
757 if( !m_tables.contains( aType ) )
758 {
759 wxLogTrace( traceLibraries, "WARNING: missing global table (%s)",
760 magic_enum::enum_name( aType ) );
761 return std::nullopt;
762 }
763
764 return m_tables.at( aType ).get();
765 }
766
768 {
769 if( !m_projectTables.contains( aType ) )
770 {
771 if( !Project().IsNullProject() )
773 else
774 return std::nullopt;
775 }
776
777 return m_projectTables.at( aType ).get();
778 }
779 }
780
781 return std::nullopt;
782}
783
784
785std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::Rows( LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope,
786 bool aIncludeInvalid ) const
787{
788 std::map<wxString, LIBRARY_TABLE_ROW*> rows;
789 std::vector<wxString> rowOrder;
790
791 std::list<std::ranges::ref_view<const std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>>> tables;
792
793 switch( aScope )
794 {
796 tables = { std::views::all( m_tables ) };
797 break;
798
800 tables = { std::views::all( m_projectTables ) };
801 break;
802
804 tables = { std::views::all( m_tables ), std::views::all( m_projectTables ) };
805 break;
806
808 wxFAIL;
809 }
810
811 std::function<void(const std::unique_ptr<LIBRARY_TABLE>&, bool parentHidden)> processTable =
812 [&]( const std::unique_ptr<LIBRARY_TABLE>& aTable, const bool parentHidden )
813 {
814 if( aTable->Type() != aType )
815 return;
816
817 if( aTable->IsOk() || aIncludeInvalid )
818 {
819 for( LIBRARY_TABLE_ROW& row : aTable->Rows() )
820 {
821 if( row.IsOk() || aIncludeInvalid )
822 {
823 // Hide child row if parent is hidden
824 if( parentHidden )
825 row.SetHidden( true );
826
827 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
828 {
829 if( !m_childTables.contains( row.URI() ) )
830 continue;
831
832 // Don't include libraries from disabled nested tables
833 if( row.Disabled() )
834 continue;
835
836 processTable( m_childTables.at( row.URI() ), row.Hidden() );
837 }
838 else
839 {
840 if( !rows.contains( row.Nickname() ) )
841 rowOrder.emplace_back( row.Nickname() );
842
843 rows[ row.Nickname() ] = &row;
844 }
845 }
846 }
847 }
848 };
849
850 for( const std::unique_ptr<LIBRARY_TABLE>& table :
851 std::views::join( tables ) | std::views::values )
852 {
853 processTable( table, false );
854 }
855
856 std::vector<LIBRARY_TABLE_ROW*> ret;
857
858 for( const wxString& row : rowOrder )
859 ret.emplace_back( rows[row] );
860
861 return ret;
862}
863
864
865std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::GetRow( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
866 LIBRARY_TABLE_SCOPE aScope )
867{
868 {
869 std::lock_guard lock( m_rowCacheMutex );
870 auto key = std::make_tuple( aType, aScope, aNickname );
871
872 if( auto it = m_rowCache.find( key ); it != m_rowCache.end() )
873 return it->second;
874 }
875
876 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
877 {
878 if( row->Nickname() == aNickname )
879 {
880 std::lock_guard lock( m_rowCacheMutex );
881 m_rowCache[std::make_tuple( aType, aScope, aNickname )] = row;
882 return row;
883 }
884 }
885
886 return std::nullopt;
887}
888
889
890std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::FindRowByURI( LIBRARY_TABLE_TYPE aType,
891 const wxString& aUri,
892 LIBRARY_TABLE_SCOPE aScope ) const
893{
894 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
895 {
896 if( UrisAreEquivalent( GetFullURI( row, true, &Project() ), aUri ) )
897 return row;
898 }
899
900 return std::nullopt;
901}
902
903
904void LIBRARY_MANAGER::ReloadLibraryEntry( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
905 LIBRARY_TABLE_SCOPE aScope )
906{
907 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
908 ( *adapter )->ReloadLibraryEntry( aNickname, aScope );
909}
910
911
913 const wxString& aNickname )
914{
915 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
916 return ( *adapter )->LoadLibraryEntry( aNickname );
917
918 return std::nullopt;
919}
920
921
922void LIBRARY_MANAGER::LoadProjectTables( const wxString& aProjectPath,
923 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
924{
925 // Cancel any in-progress loads and clear adapter caches before destroying project
926 // tables. Cached LIB_DATA entries hold raw LIBRARY_TABLE_ROW pointers into the old
927 // tables, which would dangle once loadTables() replaces them. Mirrors the safety
928 // ordering in LoadGlobalTables().
929 {
930 std::scoped_lock lock( m_adaptersMutex );
931
932 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
933 adapter->ProjectTablesChanged( aTablesToLoad );
934 }
935
936 if( wxFileName::IsDirReadable( aProjectPath ) )
937 {
938 loadTables( aProjectPath, LIBRARY_TABLE_SCOPE::PROJECT, aTablesToLoad );
939 }
940 else
941 {
942 // loadTables() would have cleared m_rowCache before rebuilding the new
943 // table; do the same here so cached entries don't point into the
944 // project tables we are about to destroy.
945 {
946 std::lock_guard lock( m_rowCacheMutex );
947 m_rowCache.clear();
948 }
949
950 if( aTablesToLoad.size() == 0 )
951 {
952 m_projectTables.clear();
953 }
954 else
955 {
956 for( LIBRARY_TABLE_TYPE type : aTablesToLoad )
957 m_projectTables.erase( type );
958 }
959
960 wxLogTrace( traceLibraries, "New project path %s is not readable, not loading project tables", aProjectPath );
961 }
962
963 // Phase 2: let adapters reconcile their cache against the rebuilt project
964 // table. This erases sentinels installed by ProjectTablesChanged() for any
965 // nickname that no longer has a project row, so a library removed from the
966 // project table stops masking a same-named global library.
967 {
968 std::scoped_lock lock( m_adaptersMutex );
969
970 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
971 adapter->ProjectTablesReloaded( aTablesToLoad );
972 }
973}
974
975
977 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
978{
979 if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
980 {
982 LoadProjectTables( aTablesToLoad );
983 }
984 else
985 {
986 LoadGlobalTables( aTablesToLoad );
987 }
988}
989
990
991std::optional<wxString> LIBRARY_MANAGER::GetFullURI( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
992 bool aSubstituted )
993{
994 if( std::optional<const LIBRARY_TABLE_ROW*> result = GetRow( aType, aNickname ) )
995 return GetFullURI( *result, aSubstituted, &Project() );
996
997 return std::nullopt;
998}
999
1000
1001wxString LIBRARY_MANAGER::GetFullURI( const LIBRARY_TABLE_ROW* aRow, bool aSubstituted,
1002 const PROJECT* aProject )
1003{
1004 if( aSubstituted )
1005 return ExpandEnvVarSubstitutions( aRow->URI(), aProject ? aProject : &Pgm().GetSettingsManager().Prj() );
1006
1007 return aRow->URI();
1008}
1009
1010
1011wxString LIBRARY_MANAGER::ExpandURI( const wxString& aShortURI, const PROJECT& aProject )
1012{
1013 wxLogNull doNotLog; // We do our own error reporting; we don't want to hear about missing envvars
1014
1015 wxFileName path( ExpandEnvVarSubstitutions( aShortURI, &aProject ) );
1016 path.MakeAbsolute();
1017 return path.GetFullPath();
1018}
1019
1020
1022{
1023 // PCM_LIB_TRAVERSER stores URIs of the form
1024 // ${KICADn_3RD_PARTY}/<category>/<pkgid>/<name>.<ext>
1025 // where <category> is one of the fixed PCM content folders. Matching this full
1026 // template is what uniquely identifies a PCM-added row. Matching only the leading
1027 // ${KICADn_3RD_PARTY} token is not sufficient because users routinely repurpose
1028 // that env var to point at their own library collection (as they did with
1029 // KICAD8_3RD_PARTY in earlier versions). A row the user added by hand under their
1030 // repurposed 3RD_PARTY directory must never be treated as PCM-managed, or the
1031 // auto-remove pass would silently delete it.
1032 const wxString& uri = aRow.URI();
1033
1034 if( !uri.StartsWith( wxS( "${" ) ) )
1035 return false;
1036
1037 size_t end = uri.find( wxS( '}' ) );
1038
1039 if( end == wxString::npos || end <= 2 )
1040 return false;
1041
1042 wxString varName = uri.SubString( 2, end - 1 );
1043
1044 if( !ENV_VAR::IsVersionedEnvVar( varName, wxS( "3RD_PARTY" ) ) )
1045 return false;
1046
1047 // PCM_LIB_TRAVERSER always joins the URI with '/', so the token must be followed by a
1048 // forward slash; a backslash or missing separator (e.g. "${KICAD10_3RD_PARTY}symbols/...")
1049 // was never emitted by PCM.
1050 if( end + 1 >= uri.length() || uri[end + 1] != wxS( '/' ) )
1051 return false;
1052
1053 // PCM_LIB_TRAVERSER nests libraries at least as <category>/<pkgid>/<library>, so there
1054 // must be a category folder, at least one package-id folder, and a library leaf, with no
1055 // empty components. A user library placed directly under the repurposed 3RD_PARTY root
1056 // (or in a same-named folder with no package level) does not match.
1057 wxArrayString parts = wxSplit( uri.Mid( end + 2 ), '/', '\0' );
1058
1059 if( parts.size() < 3 )
1060 return false;
1061
1062 for( const wxString& part : parts )
1063 {
1064 if( part.IsEmpty() )
1065 return false;
1066 }
1067
1068 wxString category = parts[0];
1069 wxString leaf = parts.Last();
1070
1071 // The leaf must carry the category-appropriate library extension over a non-empty stem;
1072 // a bare extension (e.g. a hidden ".kicad_sym") is never a PCM library and matching it
1073 // would let the auto-remove pass delete an unrelated file in a same-named folder.
1074 auto hasLibExtension = [&leaf]( const wxString& aExt )
1075 {
1076 return leaf.length() > aExt.length() && leaf.EndsWith( aExt );
1077 };
1078
1079 if( category == wxS( "symbols" ) )
1080 return hasLibExtension( wxS( ".kicad_sym" ) );
1081
1082 if( category == wxS( "footprints" ) )
1083 return hasLibExtension( wxS( ".pretty" ) );
1084
1085 if( category == wxS( "design_blocks" ) )
1086 {
1087 static const wxString designBlockExt =
1088 wxString::Format( wxS( ".%s" ), FILEEXT::KiCadDesignBlockLibPathExtension );
1089
1090 return hasLibExtension( designBlockExt );
1091 }
1092
1093 return false;
1094}
1095
1096
1097bool LIBRARY_MANAGER::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 )
1098{
1099 // Avoid comparing filenames as wxURIs
1100 if( aURI1.Find( "://" ) != wxNOT_FOUND )
1101 {
1102 // found as full path
1103 return aURI1 == aURI2;
1104 }
1105 else
1106 {
1107 const wxFileName fn1( aURI1 );
1108 const wxFileName fn2( aURI2 );
1109
1110 // This will also test if the file is a symlink so if we are comparing
1111 // a symlink to the same real file, the comparison will be true. See
1112 // wxFileName::SameAs() in the wxWidgets source.
1113
1114 // found as full path and file name
1115 return fn1 == fn2;
1116 }
1117}
1118
1119
1123
1124
1129
1130
1134
1135
1140
1141
1146
1147
1149{
1150 abortLoad();
1151
1152 // Declared before the lock so plugins die after it is released; their worker threads may need it
1153 std::vector<std::unique_ptr<IO_BASE>> retired;
1154 std::unique_lock lock( m_librariesMutex );
1155
1156 // Reset entries in place rather than erasing them. Erasing would let
1157 // fetchIfLoaded() fall through to globalLibs() for any nickname that is
1158 // shadowed by a project library, defeating the project-over-global
1159 // precedence enforced by LIBRARY_MANAGER::Rows(). ProjectTablesReloaded()
1160 // later prunes any sentinels whose nicknames are no longer in the rebuilt
1161 // project table, so stale shadowing cannot persist.
1162 for( auto& entry : m_libraries )
1163 {
1164 retired.push_back( std::move( entry.second.plugin ) );
1165 entry.second = LIB_DATA{};
1166 }
1167}
1168
1169
1174
1175
1176void LIBRARY_MANAGER_ADAPTER::GlobalTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1177{
1178 bool me = aChangedTables.size() == 0;
1179
1180 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1181 {
1182 if( type == Type() )
1183 {
1184 me = true;
1185 break;
1186 }
1187 }
1188
1189 if( !me )
1190 return;
1191
1192 abortLoad();
1193
1194 // Plugins die after unlocking; their worker threads may need the mutex
1195 std::map<wxString, LIB_DATA> retired;
1196
1197 {
1198 std::unique_lock lock( globalLibsMutex() );
1199 retired.swap( globalLibs() );
1200 }
1201}
1202
1203
1205{
1206 abortLoad();
1207
1208 // Declared before the lock so plugins die after it is released
1209 std::vector<std::map<wxString, LIB_DATA>::node_type> retired;
1210 std::unique_lock lock( globalLibsMutex() );
1211
1212 // Drop entries this adapter owns, identified by global_owner rather than the row pointer,
1213 // which another manager could reuse at the same address after this manager frees its tables.
1214 for( auto it = globalLibs().begin(); it != globalLibs().end(); )
1215 {
1216 if( it->second.global_owner == this )
1217 retired.push_back( globalLibs().extract( it++ ) );
1218 else
1219 ++it;
1220 }
1221}
1222
1223
1224void LIBRARY_MANAGER_ADAPTER::ProjectTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1225{
1226 bool me = aChangedTables.size() == 0;
1227
1228 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1229 {
1230 if( type == Type() )
1231 {
1232 me = true;
1233 break;
1234 }
1235 }
1236
1237 if( !me )
1238 return;
1239
1241}
1242
1243
1245 std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
1246{
1247 bool me = aChangedTables.size() == 0;
1248
1249 for( LIBRARY_TABLE_TYPE type : aChangedTables )
1250 {
1251 if( type == Type() )
1252 {
1253 me = true;
1254 break;
1255 }
1256 }
1257
1258 if( !me )
1259 return;
1260
1261 // Erase sentinels installed by resetProjectCache() for nicknames that no
1262 // longer appear in the rebuilt project table. Without this, a library
1263 // removed from the project would remain masked in m_libraries and hide a
1264 // same-named global library from HasLibrary() / fetchIfLoaded() / etc.
1265 // GetRow() is safe to call here: loadTables() reset m_rowCache before
1266 // building the new table, and async loads were aborted in phase 1.
1267 // Declared before the lock so any plugin loaded since the reset dies after it is released
1268 std::vector<std::map<wxString, LIB_DATA>::node_type> retired;
1269 std::unique_lock lock( m_librariesMutex );
1270
1271 for( auto it = m_libraries.begin(); it != m_libraries.end(); )
1272 {
1273 if( !m_manager.GetRow( Type(), it->first, LIBRARY_TABLE_SCOPE::PROJECT ).has_value() )
1274 retired.push_back( m_libraries.extract( it++ ) );
1275 else
1276 ++it;
1277 }
1278}
1279
1280
1282{
1283 // A disabled row is never used; skip validation so a disabled unreachable HTTP backend
1284 // can't stall the library table dialog, and clear any stale error from an earlier check
1285 if( aRow.Disabled() )
1286 {
1287 aRow.SetOk( true );
1288 aRow.SetErrorDescription( wxEmptyString );
1289 return;
1290 }
1291
1292 // Testing is expensive; skip it if we already have a library with the same
1293 // nickname and URI as the row under test
1294 if( std::optional<LIB_DATA*> libData = fetchIfLoaded( aRow.Nickname() ) )
1295 {
1296 const LIBRARY_TABLE_ROW* loadedRow = ( *libData )->row;
1297
1298 if( loadedRow->URI() == aRow.URI() && loadedRow->Type() == aRow.Type() )
1299 {
1300 aRow.SetOk( loadedRow->IsOk() );
1301 return;
1302 }
1303 }
1304
1305 abortLoad();
1306
1308
1309 if( plugin.has_value() )
1310 {
1311 LIB_DATA lib;
1312 lib.row = &aRow;
1313 lib.plugin.reset( *plugin );
1314
1315 std::optional<LIB_STATUS> status = CheckLibrary( &lib );
1316
1317 if( status.has_value() )
1318 {
1319 aRow.SetOk( status.value().load_status == LOAD_STATUS::LOADED );
1320
1321 if( status.value().error.has_value() )
1322 aRow.SetErrorDescription( status.value().error.value().message );
1323 }
1324 }
1325 else if( plugin.error().message == LIBRARY_TABLE_OK().message )
1326 {
1327 aRow.SetOk( true );
1328 aRow.SetErrorDescription( wxEmptyString );
1329 }
1330 else
1331 {
1332 aRow.SetOk( false );
1333 aRow.SetErrorDescription( plugin.error().message );
1334 }
1335}
1336
1337
1338
1340{
1341 wxCHECK( m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL ), nullptr );
1342 return *m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL );
1343}
1344
1345
1346std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER_ADAPTER::ProjectTable() const
1347{
1348 return m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::PROJECT );
1349}
1350
1351
1352std::optional<wxString> LIBRARY_MANAGER_ADAPTER::FindLibraryByURI( const wxString& aURI ) const
1353{
1354 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
1355 {
1356 if( LIBRARY_MANAGER::UrisAreEquivalent( LIBRARY_MANAGER::GetFullURI( row, true, &m_manager.Project() ), aURI ) )
1357 return row->Nickname();
1358 }
1359
1360 return std::nullopt;
1361}
1362
1363
1364std::vector<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryNames() const
1365{
1366 std::vector<wxString> ret;
1367 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
1368
1369 wxLogTrace( traceLibraries, "GetLibraryNames: checking %zu rows from table", rows.size() );
1370
1371 for( const LIBRARY_TABLE_ROW* row : rows )
1372 {
1373 wxString nickname = row->Nickname();
1374 std::optional<const LIB_DATA*> loaded = fetchIfLoaded( nickname );
1375
1376 if( loaded )
1377 ret.emplace_back( nickname );
1378 }
1379
1381
1382 wxLogTrace( traceLibraries, "GetLibraryNames: returning %zu of %zu libraries", ret.size(), rows.size() );
1383 return ret;
1384}
1385
1386
1387bool LIBRARY_MANAGER_ADAPTER::HasLibrary( const wxString& aNickname, bool aCheckEnabled ) const
1388{
1389 std::optional<const LIB_DATA*> r = fetchIfLoaded( aNickname );
1390
1391 if( r.has_value() )
1392 return !aCheckEnabled || !r.value()->row->Disabled();
1393
1394 return false;
1395}
1396
1397
1398bool LIBRARY_MANAGER_ADAPTER::DeleteLibrary( const wxString& aNickname )
1399{
1400 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1401 {
1402 LIB_DATA* data = *result;
1403 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1404
1405 // Serialize cache teardown against readers. loadIfNeeded() already dropped the manager lock
1406 // (manager > pluginMutex).
1407 std::lock_guard pluginGuard( pluginMutex( aNickname ) );
1408
1409 try
1410 {
1411 return data->plugin->DeleteLibrary( getUri( data->row ), &options );
1412 }
1413 catch( ... )
1414 {
1415 return false;
1416 }
1417 }
1418
1419 return false;
1420}
1421
1422
1423std::optional<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryDescription( const wxString& aNickname ) const
1424{
1425 if( std::optional<const LIB_DATA*> optRow = fetchIfLoaded( aNickname ); optRow )
1426 return ( *optRow )->row->Description();
1427
1428 return std::nullopt;
1429}
1430
1431
1432std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::Rows( LIBRARY_TABLE_SCOPE aScope,
1433 bool aIncludeInvalid ) const
1434{
1435 return m_manager.Rows( Type(), aScope, aIncludeInvalid );
1436}
1437
1438
1439std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::GetRow( const wxString &aNickname,
1440 LIBRARY_TABLE_SCOPE aScope ) const
1441{
1442 return m_manager.GetRow( Type(), aNickname, aScope );
1443}
1444
1445
1446std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::FindRowByURI(
1447 const wxString& aUri,
1448 LIBRARY_TABLE_SCOPE aScope ) const
1449{
1450 return m_manager.FindRowByURI( Type(), aUri, aScope );
1451}
1452
1453
1455{
1456 {
1457 std::lock_guard lock( m_loadMutex );
1458
1459 if( m_futures.empty() )
1460 return;
1461
1462 wxLogTrace( traceLibraries, "Aborting library load..." );
1463 m_abort.store( true );
1464 }
1465
1467 wxLogTrace( traceLibraries, "Aborted" );
1468
1469 {
1470 std::lock_guard lock( m_loadMutex );
1471 m_abort.store( false );
1472 m_futures.clear();
1473 m_loadTotal.store( 0 );
1474 m_loadCount.store( 0 );
1475 }
1476}
1477
1478
1480{
1481 size_t total = m_loadTotal.load();
1482
1483 if( total == 0 )
1484 return std::nullopt;
1485
1486 size_t loaded = m_loadCount.load();
1487 return loaded / static_cast<float>( total );
1488}
1489
1490
1492{
1493 wxLogTrace( traceLibraries, "BlockUntilLoaded: entry, acquiring m_loadMutex" );
1494 std::unique_lock<std::mutex> asyncLock( m_loadMutex );
1495
1496 wxLogTrace( traceLibraries, "BlockUntilLoaded: waiting on %zu futures", m_futures.size() );
1497
1498 for( const std::future<void>& future : m_futures )
1499 future.wait();
1500
1501 wxLogTrace( traceLibraries, "BlockUntilLoaded: all futures complete, loadCount=%zu, loadTotal=%zu",
1502 m_loadCount.load(), m_loadTotal.load() );
1503}
1504
1505
1506bool LIBRARY_MANAGER_ADAPTER::IsLibraryLoaded( const wxString& aNickname )
1507{
1508 {
1509 std::shared_lock lock( m_librariesMutex );
1510
1511 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1512 return it->second.status.load_status == LOAD_STATUS::LOADED;
1513 }
1514
1515 {
1516 std::shared_lock lock( globalLibsMutex() );
1517
1518 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1519 return it->second.status.load_status == LOAD_STATUS::LOADED;
1520 }
1521
1522 return false;
1523}
1524
1525
1526std::optional<LIBRARY_ERROR> LIBRARY_MANAGER_ADAPTER::LibraryError( const wxString& aNickname ) const
1527{
1528 {
1529 std::shared_lock lock( m_librariesMutex );
1530
1531 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1532 return it->second.status.error;
1533 }
1534
1535 {
1536 std::shared_lock lock( globalLibsMutex() );
1537
1538 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1539 return it->second.status.error;
1540 }
1541
1542 return std::nullopt;
1543}
1544
1545
1546std::vector<std::pair<wxString, LIB_STATUS>> LIBRARY_MANAGER_ADAPTER::GetLibraryStatuses() const
1547{
1548 std::vector<std::pair<wxString, LIB_STATUS>> ret;
1549
1550 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
1551 {
1552 if( row->Disabled() )
1553 continue;
1554
1555 if( std::optional<LIB_STATUS> result = GetLibraryStatus( row->Nickname() ) )
1556 {
1557 ret.emplace_back( std::make_pair( row->Nickname(), *result ) );
1558 }
1559 else
1560 {
1561 // The row came from the library table, so the missing entry means the async
1562 // preload has not reached it yet (or was pre-empted by a project change), not
1563 // that the library is absent. Reporting a load error here surfaces a spurious
1564 // error indicator while background loading races schematic auto-open. Genuine
1565 // failures carry their own LOAD_ERROR entry, and the library still loads on
1566 // demand, so report it as not-yet-loaded instead.
1567 ret.emplace_back( std::make_pair( row->Nickname(),
1568 LIB_STATUS( { .load_status = LOAD_STATUS::INVALID } ) ) );
1569 }
1570 }
1571
1572 return ret;
1573}
1574
1575
1577{
1578 std::vector<KI_ERROR> errors;
1579
1580 for( const auto& [nickname, status] : GetLibraryStatuses() )
1581 {
1582 if( status.load_status == LOAD_STATUS::LOAD_ERROR && status.error )
1583 {
1584 wxString title = status.error->message.BeforeFirst( '\n' );
1585 title.StartsWith( wxS( "IO_ERROR: " ), &title );
1586 errors.emplace_back(
1587 KI_ERROR( RPT_SEVERITY_ERROR, wxString::Format( _( "Error loading library '%s'" ), nickname ) )
1588 .SetDescription( title )
1589#ifdef DEBUG
1590 .SetDebugText( status.error->details )
1591#endif
1592 );
1593 }
1594 }
1595
1596 return errors;
1597}
1598
1599
1600std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::LoadLibraryEntry( const wxString& aNickname )
1601{
1603
1604 if( result.has_value() )
1605 return LoadOne( *result );
1606
1607 return std::nullopt;
1608}
1609
1610
1612{
1613 // Drain the async load before erasing, else a worker mid-enumerate writes status through the
1614 // freed LIB_DATA (every other invalidator already does this).
1615 abortLoad();
1616
1617 auto reloadScope =
1618 [&]( LIBRARY_TABLE_SCOPE aScopeToReload, std::map<wxString, LIB_DATA>& aTarget,
1619 std::shared_mutex& aMutex )
1620 {
1621 bool wasLoaded = false;
1622 std::map<wxString, LIB_DATA>::node_type retired;
1623
1624 {
1625 std::unique_lock lock( aMutex );
1626 auto it = aTarget.find( aNickname );
1627
1628 if( it != aTarget.end() && it->second.plugin )
1629 {
1630 wasLoaded = true;
1631 retired = aTarget.extract( it );
1632 }
1633 }
1634
1635 // Destroyed unlocked since the plugin's worker thread may be blocked on aMutex
1636 retired = {};
1637
1638 if( wasLoaded )
1639 {
1640 LIBRARY_RESULT<LIB_DATA*> result = loadFromScope( aNickname, aScopeToReload, aTarget, aMutex );
1641
1642 if( result.has_value() && *result )
1643 {
1644 LoadOne( *result );
1645 }
1646 else if( !result.has_value() )
1647 {
1648 wxLogTrace( traceLibraries, "ReloadLibraryEntry: failed to reload %s (%s): %s",
1649 aNickname, magic_enum::enum_name( aScopeToReload ),
1650 result.error().message );
1651 }
1652 }
1653 };
1654
1655 switch( aScope )
1656 {
1659 break;
1660
1663 break;
1664
1669 break;
1670 }
1671}
1672
1673
1674bool LIBRARY_MANAGER_ADAPTER::IsWritable( const wxString& aNickname ) const
1675{
1676 if( std::optional<const LIB_DATA*> result = fetchIfLoaded( aNickname ) )
1677 {
1678 const LIB_DATA* rowData = *result;
1679
1680 // IsLibraryWritable() may rebuild the cache via validateCache(); serialize against readers.
1681 // fetchIfLoaded() already dropped the manager lock.
1682 std::lock_guard pluginGuard( pluginMutex( aNickname ) );
1683
1684 return rowData->plugin->IsLibraryWritable( getUri( rowData->row ) );
1685 }
1686
1687 return false;
1688}
1689
1690
1691bool LIBRARY_MANAGER_ADAPTER::CreateLibrary( const wxString& aNickname )
1692{
1693 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1694 {
1695 LIB_DATA* data = *result;
1696 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1697
1698 // Serialize cache replacement against readers; loadIfNeeded() already dropped the manager lock.
1699 std::lock_guard pluginGuard( pluginMutex( aNickname ) );
1700
1701 try
1702 {
1703 data->plugin->CreateLibrary( getUri( data->row ), &options );
1704 return true;
1705 }
1706 catch( const IO_ERROR& ioe )
1707 {
1708 wxLogTrace( traceLibraries, "CreateLibrary: IO_ERROR for %s: %s",
1709 aNickname, ioe.What() );
1710 return false;
1711 }
1712 catch( const std::exception& e )
1713 {
1714 wxLogTrace( traceLibraries, "CreateLibrary: std::exception for %s: %s",
1715 aNickname, e.what() );
1716 return false;
1717 }
1718 }
1719
1720 wxLogTrace( traceLibraries, "CreateLibrary: library row '%s' not found", aNickname );
1721 return false;
1722}
1723
1724
1726{
1727 return LIBRARY_MANAGER::ExpandURI( aRow->URI(), m_manager.Project() );
1728}
1729
1730
1731std::optional<const LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname ) const
1732{
1733 {
1734 std::shared_lock lock( m_librariesMutex );
1735
1736 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1737 {
1738 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1739 return &it->second;
1740
1741 return std::nullopt;
1742 }
1743 }
1744
1745 if( m_manager.GetRow( Type(), aNickname, LIBRARY_TABLE_SCOPE::PROJECT ) )
1746 return std::nullopt; // project row exists but is not loaded
1747
1748 {
1749 std::shared_lock lock( globalLibsMutex() );
1750
1751 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1752 {
1753 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1754 return &it->second;
1755
1756 return std::nullopt;
1757 }
1758 }
1759
1760 return std::nullopt;
1761}
1762
1763
1764std::optional<LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname )
1765{
1766 {
1767 std::shared_lock lock( m_librariesMutex );
1768
1769 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1770 {
1771 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1772 return &it->second;
1773
1774 return std::nullopt;
1775 }
1776 }
1777
1778 if( m_manager.GetRow( Type(), aNickname, LIBRARY_TABLE_SCOPE::PROJECT ) )
1779 return std::nullopt; // project row exists but is not loaded
1780
1781 {
1782 std::shared_lock lock( globalLibsMutex() );
1783
1784 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1785 {
1786 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1787 return &it->second;
1788
1789 return std::nullopt;
1790 }
1791 }
1792
1793 return std::nullopt;
1794}
1795
1796
1798 LIBRARY_TABLE_SCOPE aScope,
1799 std::map<wxString, LIB_DATA>& aTarget,
1800 std::shared_mutex& aMutex )
1801{
1802 bool present = false;
1803
1804 {
1805 std::shared_lock lock( aMutex );
1806 present = aTarget.contains( aNickname ) && aTarget.at( aNickname ).plugin;
1807 }
1808
1809 if( !present )
1810 {
1811 if( auto result = m_manager.GetRow( Type(), aNickname, aScope ) )
1812 {
1813 const LIBRARY_TABLE_ROW* row = *result;
1814 wxLogTrace( traceLibraries, "Library %s (%s) not yet loaded, will attempt...",
1815 aNickname, magic_enum::enum_name( aScope ) );
1816
1817 if( LIBRARY_RESULT<IO_BASE*> plugin = createPlugin( row ); plugin.has_value() )
1818 {
1819 // Declared before the lock so a losing candidate dies unlocked; its worker may need the mutex
1820 std::unique_ptr<IO_BASE> candidate( *plugin );
1821 std::unique_lock lock( aMutex );
1822 LIB_DATA& slot = aTarget[row->Nickname()];
1823
1824 // Another creator may have installed a plugin while the lock was released
1825 if( !slot.plugin )
1826 {
1828 slot.row = row;
1829 slot.plugin = std::move( candidate );
1830
1831 if( aScope == LIBRARY_TABLE_SCOPE::GLOBAL )
1832 slot.global_owner = this;
1833 }
1834
1835 return &slot;
1836 }
1837 else
1838 {
1839 return tl::unexpected( plugin.error() );
1840 }
1841 }
1842
1843 return nullptr;
1844 }
1845
1846 std::shared_lock lock( aMutex );
1847 return &aTarget.at( aNickname );
1848}
1849
1850
1852{
1855
1856 if( !result.has_value() || *result )
1857 return result;
1858
1860
1861 if( !result.has_value() || *result )
1862 return result;
1863
1864 wxString msg = wxString::Format( _( "Library %s not found" ), aNickname );
1865 return tl::unexpected( LIBRARY_ERROR( msg ) );
1866}
1867
1868
1869std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::GetLibraryStatus( const wxString& aNickname ) const
1870{
1871 {
1872 std::shared_lock lock( m_librariesMutex );
1873
1874 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1875 return it->second.status;
1876 }
1877
1878 {
1879 std::shared_lock lock( globalLibsMutex() );
1880
1881 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1882 return it->second.status;
1883 }
1884
1885 return std::nullopt;
1886}
1887
1888
1890{
1891 std::unique_lock<std::mutex> asyncLock( m_loadMutex, std::try_to_lock );
1892
1893 if( !asyncLock )
1894 return;
1895
1896 std::erase_if( m_futures,
1897 []( std::future<void>& aFuture )
1898 {
1899 return aFuture.valid()
1900 && aFuture.wait_for( 0s ) == std::future_status::ready;
1901 } );
1902
1903 if( !m_futures.empty() )
1904 {
1905 wxLogTrace( traceLibraries, "Cannot AsyncLoad, futures from a previous call remain!" );
1906 return;
1907 }
1908
1909 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
1910
1911 m_loadTotal.store( rows.size() );
1912 m_loadCount.store( 0 );
1913
1914 if( rows.empty() )
1915 {
1916 wxLogTrace( traceLibraries, "AsyncLoad: no libraries left to load; exiting" );
1917 return;
1918 }
1919
1921
1922 auto check =
1923 [&]( const wxString& aLib, std::map<wxString, LIB_DATA>& aMap, std::shared_mutex& aMutex )
1924 {
1925 std::shared_lock lock( aMutex );
1926
1927 if( auto it = aMap.find( aLib ); it != aMap.end() )
1928 {
1929 LOAD_STATUS status = it->second.status.load_status;
1930
1931 if( status == LOAD_STATUS::LOADED || status == LOAD_STATUS::LOADING )
1932 return true;
1933 }
1934
1935 return false;
1936 };
1937
1938 // Collect work items with pre-resolved URIs. URI expansion accesses PROJECT data
1939 // (text variables, env vars) that is not thread-safe, so resolve on the calling thread.
1940 struct LOAD_WORK
1941 {
1942 wxString nickname;
1943 LIBRARY_TABLE_SCOPE scope;
1944 wxString uri;
1945 };
1946
1947 auto workQueue = std::make_shared<std::vector<LOAD_WORK>>();
1948 workQueue->reserve( rows.size() );
1949
1950 for( const LIBRARY_TABLE_ROW* row : rows )
1951 {
1952 wxString nickname = row->Nickname();
1953 LIBRARY_TABLE_SCOPE scope = row->Scope();
1954
1955 // Disabled libraries must never be contacted; skipping here keeps a broken or slow
1956 // backend (e.g. an unreachable HTTP library) from blocking startup loads
1957 if( row->Disabled() )
1958 {
1959 m_loadTotal.fetch_sub( 1 );
1960 continue;
1961 }
1962
1963 if( check( nickname, m_libraries, m_librariesMutex ) )
1964 {
1965 m_loadTotal.fetch_sub( 1 );
1966 continue;
1967 }
1968
1969 if( check( nickname, globalLibs(), globalLibsMutex() ) && scope != LIBRARY_TABLE_SCOPE::PROJECT )
1970 {
1971 m_loadTotal.fetch_sub( 1 );
1972 continue;
1973 }
1974
1975 workQueue->emplace_back( LOAD_WORK{ nickname, scope, getUri( row ) } );
1976 }
1977
1978 if( workQueue->empty() )
1979 {
1980 wxLogTrace( traceLibraries, "AsyncLoad: all libraries already loaded; exiting" );
1981 return;
1982 }
1983
1984 // Cap loading threads to leave headroom for the GUI and other thread pool work.
1985 // Each worker pulls libraries from a shared queue, so we submit fewer tasks than
1986 // libraries and avoid flooding the pool.
1987 size_t poolSize = tp.get_thread_count();
1988 size_t maxLoadThreads = std::max<size_t>( 1, poolSize > 2 ? poolSize - 2 : 1 );
1989 size_t numWorkers = std::min( maxLoadThreads, workQueue->size() );
1990
1991 auto workIndex = std::make_shared<std::atomic<size_t>>( 0 );
1992
1993 wxLogTrace( traceLibraries, "AsyncLoad: %zu libraries to load, using %zu worker threads (pool has %zu)",
1994 workQueue->size(), numWorkers, poolSize );
1995
1996 for( size_t w = 0; w < numWorkers; ++w )
1997 {
1998 m_futures.emplace_back( tp.submit_task(
1999 [this, workQueue, workIndex]()
2000 {
2001 while( true )
2002 {
2003 if( m_abort.load() )
2004 return;
2005
2006 size_t idx = workIndex->fetch_add( 1 );
2007
2008 if( idx >= workQueue->size() )
2009 return;
2010
2011 const LOAD_WORK& work = ( *workQueue )[idx];
2012 LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( work.nickname );
2013
2014 if( result.has_value() )
2015 {
2016 LIB_DATA* lib = *result;
2017
2018 try
2019 {
2020 {
2021 std::unique_lock lock(
2022 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
2023 ? globalLibsMutex()
2024 : m_librariesMutex );
2025 lib->status.load_status = LOAD_STATUS::LOADING;
2026 }
2027
2028 enumerateLibrary( lib, work.uri );
2029
2030 {
2031 std::unique_lock lock(
2032 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
2033 ? globalLibsMutex()
2034 : m_librariesMutex );
2035 lib->status.load_status = LOAD_STATUS::LOADED;
2036 }
2037 }
2038 catch( IO_ERROR& e )
2039 {
2040 std::unique_lock lock(
2041 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
2042 ? globalLibsMutex()
2043 : m_librariesMutex );
2044 lib->status.load_status = LOAD_STATUS::LOAD_ERROR;
2045 lib->status.error = LIBRARY_ERROR( e.Problem(), e.Where() );
2046 wxLogTrace( traceLibraries, "%s: plugin threw exception: %s",
2047 work.nickname, e.What() );
2048 }
2049 }
2050 else
2051 {
2052 std::unique_lock lock(
2053 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
2054 ? globalLibsMutex()
2055 : m_librariesMutex );
2056
2057 std::map<wxString, LIB_DATA>& target =
2058 ( work.scope == LIBRARY_TABLE_SCOPE::GLOBAL ) ? globalLibs()
2059 : m_libraries;
2060
2061 target[work.nickname].status = LIB_STATUS( {
2062 .load_status = LOAD_STATUS::LOAD_ERROR,
2063 .error = result.error()
2064 } );
2065 }
2066
2067 ++m_loadCount;
2068 }
2069 },
2070 BS::pr::lowest ) );
2071 }
2072
2073 wxLogTrace( traceLibraries, "Started async load of %zu libraries", workQueue->size() );
2074}
const char * name
virtual bool DeleteLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Delete an existing library and returns true, or if library does not exist returns false,...
Definition io_base.cpp:53
virtual bool IsLibraryWritable(const wxString &aLibraryPath)
Return true if the library at aLibraryPath is writable.
Definition io_base.cpp:60
virtual void CreateLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Create a new empty library at aLibraryPath empty.
Definition io_base.cpp:46
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()
wxString m_PcmLibPrefix
std::map< wxString, std::map< wxString, LIB_OVERRIDE > > m_LibOverrides
Overrides for libraries in read-only nested tables.
Holds a structured error message.
Definition ki_error.h:37
The interface used by the classes that actually can load IO plugins for the different parts of KiCad ...
std::optional< float > AsyncLoadProgress() const
Returns the fraction of the libraries queued by AsyncLoad() that have been processed,...
virtual std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib)=0
std::vector< KI_ERROR > GetLibraryLoadErrors() const
Returns all library load errors as newline-separated strings for display.
void resetProjectCache()
Aborts pending loads and resets every project-scope cache entry in place (plugin and row cleared,...
virtual std::optional< LIBRARY_ERROR > LibraryError(const wxString &aNickname) const
void ReloadLibraryEntry(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
Synchronously recreates the plugin for and reloads the given library.
std::optional< LIB_STATUS > GetLibraryStatus(const wxString &aNickname) const
Returns the status of a loaded library, or nullopt if the library hasn't been loaded (yet)
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
void AbortAsyncLoad()
Aborts any async load in progress; blocks until fully done aborting.
void ProjectTablesChanged(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Notify the adapter that the project library tables are about to be rebuilt.
LIBRARY_TABLE * GlobalTable() const
Retrieves the global library table for this adapter type.
virtual std::shared_mutex & globalLibsMutex()=0
void evictOwnedGlobalEntries()
Erases this adapter's own entries (LIB_DATA::global_owner == this) from the process-wide globalLibs()...
LIBRARY_MANAGER_ADAPTER(LIBRARY_MANAGER &aManager)
Constructs a type-specific adapter into the library manager.
void CheckTableRow(LIBRARY_TABLE_ROW &aRow)
bool IsLibraryLoaded(const wxString &aNickname)
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Like LIBRARY_MANAGER::Rows but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< LIBRARY_TABLE_ROW * > FindRowByURI(const wxString &aUri, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::FindRowByURI but filtered to the LIBRARY_TABLE_TYPE of this adapter.
wxString getUri(const LIBRARY_TABLE_ROW *aRow) const
virtual std::map< wxString, LIB_DATA > & globalLibs()=0
virtual LIBRARY_TABLE_TYPE Type() const =0
The type of library table this adapter works with.
bool DeleteLibrary(const wxString &aNickname)
Deletes the given library from disk if it exists; returns true if deleted.
LIBRARY_RESULT< LIB_DATA * > loadIfNeeded(const wxString &aNickname)
Fetches a loaded library, triggering a load of that library if it isn't loaded yet.
std::optional< wxString > FindLibraryByURI(const wxString &aURI) const
std::shared_mutex m_librariesMutex
LIBRARY_MANAGER & Manager() const
void abortLoad()
Aborts any async load in progress; blocks until fully done aborting.
std::optional< wxString > GetLibraryDescription(const wxString &aNickname) const
virtual LIBRARY_RESULT< IO_BASE * > createPlugin(const LIBRARY_TABLE_ROW *row)=0
Creates a concrete plugin for the given row.
void GlobalTablesChanged(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Notify the adapter that the global library tables have changed.
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::map< wxString, LIB_DATA > m_libraries
virtual IO_BASE * plugin(const LIB_DATA *aRow)=0
std::vector< wxString > GetLibraryNames() const
Returns a list of library nicknames that are available (skips any that failed to load)
virtual void ProjectChanged()
Notify the adapter that the active project has changed.
LIBRARY_RESULT< LIB_DATA * > loadFromScope(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope, std::map< wxString, LIB_DATA > &aTarget, std::shared_mutex &aMutex)
virtual std::optional< LIB_STATUS > CheckLibrary(LIB_DATA *aLib)
Validates that a library is loadable. May not necessarily load the library!
std::vector< std::future< void > > m_futures
static std::recursive_mutex & pluginMutex(const wxString &aNickname)
Serializes access to a library's shared plugin instance so its single mutable cache is not raced by c...
bool CreateLibrary(const wxString &aNickname)
Creates the library (i.e. saves to disk) for the given row if it exists.
void AsyncLoad()
Loads all available libraries for this adapter type in the background.
virtual bool IsWritable(const wxString &aNickname) const
Return true if the given nickname exists and is not a read-only library.
std::vector< std::pair< wxString, LIB_STATUS > > GetLibraryStatuses() const
Returns a list of all library nicknames and their status (even if they failed to load)
std::atomic< size_t > m_loadTotal
void ProjectTablesReloaded(std::initializer_list< LIBRARY_TABLE_TYPE > aChangedTables={})
Complements ProjectTablesChanged by erasing project-scope cache entries whose nicknames no longer app...
std::atomic< size_t > m_loadCount
LIBRARY_MANAGER & m_manager
std::optional< LIB_STATUS > LoadLibraryEntry(const wxString &aNickname)
Synchronously loads the named library to LOADED state.
std::optional< const LIB_DATA * > fetchIfLoaded(const wxString &aNickname) const
void ReloadTables(LIBRARY_TABLE_SCOPE aScope, std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
static wxString StockTableTokenizedURI(LIBRARY_TABLE_TYPE aType)
static wxString ExpandURI(const wxString &aShortURI, const PROJECT &aProject)
const PROJECT *const m_project
std::mutex m_rowCacheMutex
void ClearLibOverride(const wxString &aTablePath, const wxString &aNickname)
Removes any override for a library that no longer needs one.
std::optional< LIBRARY_MANAGER_ADAPTER * > Adapter(LIBRARY_TABLE_TYPE aType) const
void applyLibOverrides(LIBRARY_TABLE &aTable)
Applies user overrides (disabled/hidden) to rows of a read-only nested table.
std::map< wxString, std::unique_ptr< LIBRARY_TABLE > > m_childTables
Map of full URI to table object for tables that are referenced by global or project tables.
void loadNestedTables(LIBRARY_TABLE &aTable)
std::map< ROW_CACHE_KEY, LIBRARY_TABLE_ROW * > m_rowCache
bool RemoveAdapter(LIBRARY_TABLE_TYPE aType, LIBRARY_MANAGER_ADAPTER *aAdapter)
const PROJECT & Project() const
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.
static bool UrisAreEquivalent(const wxString &aURI1, const wxString &aURI2)
std::mutex m_adaptersMutex
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 ApplyLibOverrides(LIBRARY_TABLE &aTable)
Applies stored user overrides (disabled/hidden) to rows of a read-only table.
void RegisterAdapter(LIBRARY_TABLE_TYPE aType, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > &&aAdapter)
static wxString DefaultGlobalTablePath(LIBRARY_TABLE_TYPE aType)
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false)
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
static std::vector< LIBRARY_TABLE_TYPE > InvalidGlobalTables()
void createEmptyTable(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
void AbortAsyncLoads()
Abort any async library loading operations in progress.
std::optional< LIB_STATUS > LoadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname)
Synchronously loads the named library to LOADED state for the given type.
static bool GlobalTablesValid()
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_TABLE > > m_projectTables
std::optional< LIBRARY_TABLE_ROW * > FindRowByURI(LIBRARY_TABLE_TYPE aType, const wxString &aUri, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
void SetLibOverride(const wxString &aTablePath, const wxString &aNickname, bool aDisabled, bool aHidden)
Set a user override for a library in a read-only nested table.
bool IsProjectScoped() const
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
static bool CreateGlobalTable(LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries)
void loadTables(const wxString &aTablePath, LIBRARY_TABLE_SCOPE aScope, std::vector< LIBRARY_TABLE_TYPE > aTablesToLoad={})
static wxString tableFileName(LIBRARY_TABLE_TYPE aType)
static bool IsPcmManagedRow(const LIBRARY_TABLE_ROW &aRow)
Return true if a library table row was added by the Plugin and Content Manager.
std::vector< LIBRARY_TABLE_ROW * > Rows(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH, bool aIncludeInvalid=false) const
Returns a flattened list of libraries of the given type.
void LoadGlobalTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the global library tables in the given list, or all tables if no list is given
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_TABLE > > m_tables
static wxString StockTableReferenceURI(LIBRARY_TABLE_TYPE aType)
static wxString StockTablePath(LIBRARY_TABLE_TYPE aType)
std::optional< LIBRARY_TABLE_ROW * > GetRow(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
void ProjectChanged()
Notify all adapters that the project has changed.
static bool IsTableValid(const wxString &aPath)
LIBRARY_MANAGER(const PROJECT *aProject=nullptr)
An explicit project is borrowed until this manager and its adapters are destroyed.
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_MANAGER_ADAPTER > > m_adapters
void SetNickname(const wxString &aNickname)
void SetOk(bool aOk=true)
void SetType(const wxString &aType)
bool Disabled() const
void SetErrorDescription(const wxString &aDescription)
std::map< std::string, UTF8 > GetOptionsMap() const
void SetDescription(const wxString &aDescription)
const wxString & Type() const
static const wxString TABLE_TYPE_NAME
void SetURI(const wxString &aUri)
bool IsOk() const
const wxString & URI() const
const wxString & Nickname() const
bool IsReadOnly() const
Returns true if the underlying file exists but is not writable.
LIBRARY_TABLE_ROW & InsertRow()
Builds a new row and inserts it at the end of the table; returning a reference to the row.
const wxString & Path() const
LIBRARY_TABLE_SCOPE Scope() const
bool HasRow(const wxString &aNickname) const
bool HasRowWithURI(const wxString &aUri, const PROJECT &aProject, bool aSubstituted=false) const
Returns true if the given (fully-expanded) URI exists as a library in this table.
const std::deque< LIBRARY_TABLE_ROW > & Rows() const
bool IsOk() const
static wxString GetStockTemplatesPath()
Gets the stock (install) templates path.
Definition paths.cpp:317
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:586
LIBRARY_TABLE * m_designBlockTable
std::set< LIBRARY_TABLE * > m_modified
LIBRARY_MANAGER & m_manager
std::set< LIBRARY_TABLE * > Modified() const
const PROJECT & m_project
PCM_LIB_TRAVERSER(const wxString &aBasePath, LIBRARY_MANAGER &aManager, const wxString &aPrefix)
void ensureUnique(LIBRARY_TABLE *aTable, const wxString &aBaseName, wxString &aNickname) const
LIBRARY_TABLE * m_symbolTable
wxDirTraverseResult OnDir(const wxString &dirPath) override
Handles footprint library and design block library directories, minimum nest level 3.
void addRowIfNecessary(LIBRARY_TABLE *aTable, const wxFileName &aSource, ADD_MODE aMode, int aExtensionLength)
LIBRARY_TABLE * m_fpTable
wxDirTraverseResult OnFile(const wxString &aFilePath) override
Handles symbol library files, minimum nest level 2.
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:808
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
Container for project specific data.
Definition project.h:63
T * GetAppSettings(const char *aFilename)
Return a handle to the a given settings by type.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
static void ResolvePossibleSymlinks(wxFileName &aFilename)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
#define _(s)
Functions related to environment variables, including help functions.
static const std::string KiCadDesignBlockLibPathExtension
static const std::string SymbolLibraryTableFileName
static const std::string DesignBlockLibraryTableFileName
static const std::string FootprintLibraryTableFileName
const wxChar *const traceLibraries
Flag to enable library table and library manager tracing.
std::string message
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
PROJECT & Prj()
Definition kicad.cpp:737
LOAD_STATUS
Status of a library load managed by a library adapter.
tl::expected< ResultType, LIBRARY_ERROR > LIBRARY_RESULT
LIBRARY_TABLE_TYPE
LIBRARY_TABLE_SCOPE
KICOMMON_API bool IsVersionedEnvVar(const wxString &aName, const wxString &aBaseName)
Test whether a name is a versioned KiCad environment variable for the given base, i....
Definition env_vars.cpp:87
KICOMMON_API std::optional< wxString > GetVersionedEnvVarValue(const std::map< wxString, ENV_VAR_ITEM > &aMap, const wxString &aBaseName)
Attempt to retrieve the value of a versioned environment variable, such as KICAD8_TEMPLATE_DIR.
Definition env_vars.cpp:103
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:78
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_ERROR
void StrNumSort(T &aList, CASE_SENSITIVITY aCaseSensitivity)
Sort a container of wxString objects, in place, using the StrNumCmp() function.
wxString message
std::vector< LIBRARY_TABLE > tables
Storage for an actual loaded library (including library content owned by the plugin)
LIB_STATUS status
std::unique_ptr< IO_BASE > plugin
Never destroyed while holding the map's mutex; a plugin's worker thread may be blocked on it.
const LIBRARY_MANAGER_ADAPTER * global_owner
const LIBRARY_TABLE_ROW * row
The overall status of a loaded or loading library.
LOAD_STATUS load_status
std::string path
VECTOR2I end
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:27
wxLogTrace helper definitions.
Definition of file extensions used in Kicad.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35