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