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