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