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