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