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