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 }
430 catch( IO_ERROR& e )
431 {
432 wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() );
433 return false;
434 }
435
436 return true;
437}
438
439
440void LIBRARY_MANAGER::LoadGlobalTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
441{
442 // Cancel any in-progress load
443 {
444 std::scoped_lock lock( m_adaptersMutex );
445
446 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
447 adapter->GlobalTablesChanged( aTablesToLoad );
448 }
449
451
453 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
454
455 wxCHECK( settings, /* void */ );
456
457 const ENV_VAR_MAP& vars = Pgm().GetLocalEnvVariables();
458 std::optional<wxString> packagesPath = ENV_VAR::GetVersionedEnvVarValue( vars, wxT( "3RD_PARTY" ) );
459
460 if( packagesPath && settings->m_PcmLibAutoAdd )
461 {
462 // Scan for libraries in PCM packages directory
463 wxFileName d( *packagesPath, "" );
464
465 if( d.DirExists() )
466 {
467 PCM_LIB_TRAVERSER traverser( *packagesPath, *this, settings->m_PcmLibPrefix );
468 wxDir dir( d.GetPath() );
469
470 dir.Traverse( traverser );
471
472 for( LIBRARY_TABLE* table : traverser.Modified() )
473 {
474 table->Save().map_error(
475 []( const LIBRARY_ERROR& aError )
476 {
477 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-add: %s" ),
478 aError.message );
479 } );
480 }
481 }
482 }
483
484 auto cleanupRemovedPCMLibraries =
485 [&]( LIBRARY_TABLE_TYPE aType )
486 {
487 LIBRARY_TABLE* table = Table( aType, LIBRARY_TABLE_SCOPE::GLOBAL ).value_or( nullptr );
488 wxCHECK( table, /* void */ );
489
490 auto toErase = std::ranges::remove_if( table->Rows(),
491 [&]( const LIBRARY_TABLE_ROW& aRow )
492 {
493 wxString path = GetFullURI( &aRow, true );
494
495 return path.StartsWith( *packagesPath )
496 && !wxFileName::Exists( path );
497 } );
498
499 bool hadRemovals = !toErase.empty();
500 table->Rows().erase( toErase.begin(), toErase.end() );
501
502 if( hadRemovals )
503 {
504 table->Save().map_error(
505 []( const LIBRARY_ERROR& aError )
506 {
507 wxLogTrace( traceLibraries, wxT( "Warning: save failed after PCM auto-remove: %s" ),
508 aError.message );
509 } );
510 }
511 };
512
513 if( packagesPath && settings->m_PcmLibAutoRemove )
514 {
515 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::SYMBOL );
516 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::FOOTPRINT );
517 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::DESIGN_BLOCK );
518 }
519}
520
521
522void LIBRARY_MANAGER::LoadProjectTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
523{
524 LoadProjectTables( Pgm().GetSettingsManager().Prj().GetProjectDirectory(), aTablesToLoad );
525}
526
527
529{
530 // Abort any running async library loads before reloading project tables.
531 // Background workers hold raw LIBRARY_TABLE_ROW pointers that become dangling
532 // when loadTables() destroys and replaces the table objects.
534
535 LoadProjectTables( Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
536
537 std::scoped_lock lock( m_adaptersMutex );
538
539 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
540 adapter->ProjectChanged();
541}
542
543
545{
546 std::scoped_lock lock( m_adaptersMutex );
547
548 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
549 adapter->AbortAsyncLoad();
550}
551
552
554 std::unique_ptr<LIBRARY_MANAGER_ADAPTER>&& aAdapter )
555{
556 std::scoped_lock lock( m_adaptersMutex );
557
558 wxCHECK_MSG( !m_adapters.contains( aType ), /**/, "You should only register an adapter once!" );
559
560 m_adapters[aType] = std::move( aAdapter );
561}
562
563
565{
566 std::scoped_lock lock( m_adaptersMutex );
567 if( !m_adapters.contains( aType ) )
568 return false;
569
570 if( m_adapters[aType].get() != aAdapter )
571 return false;
572
573 m_adapters.erase( aType );
574 return true;
575}
576
577
578std::optional<LIBRARY_MANAGER_ADAPTER*> LIBRARY_MANAGER::Adapter( LIBRARY_TABLE_TYPE aType ) const
579{
580 std::scoped_lock lock( m_adaptersMutex );
581
582 if( m_adapters.contains( aType ) )
583 return m_adapters.at( aType ).get();
584
585 return std::nullopt;
586}
587
588
589std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER::Table( LIBRARY_TABLE_TYPE aType,
590 LIBRARY_TABLE_SCOPE aScope )
591{
592 switch( aScope )
593 {
596 wxCHECK_MSG( false, std::nullopt, "Table() requires a single scope" );
597
599 {
600 if( !m_tables.contains( aType ) )
601 {
602 wxLogTrace( traceLibraries, "WARNING: missing global table (%s)",
603 magic_enum::enum_name( aType ) );
604 return std::nullopt;
605 }
606
607 return m_tables.at( aType ).get();
608 }
609
611 {
612 // TODO: handle multiple projects
613 if( !m_projectTables.contains( aType ) )
614 {
615 if( !Pgm().GetSettingsManager().Prj().IsNullProject() )
617 else
618 return std::nullopt;
619 }
620
621 return m_projectTables.at( aType ).get();
622 }
623 }
624
625 return std::nullopt;
626}
627
628
629std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::Rows( LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope,
630 bool aIncludeInvalid ) const
631{
632 std::map<wxString, LIBRARY_TABLE_ROW*> rows;
633 std::vector<wxString> rowOrder;
634
635 std::list<std::ranges::ref_view<const std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>>> tables;
636
637 switch( aScope )
638 {
640 tables = { std::views::all( m_tables ) };
641 break;
642
644 tables = { std::views::all( m_projectTables ) };
645 break;
646
648 tables = { std::views::all( m_tables ), std::views::all( m_projectTables ) };
649 break;
650
652 wxFAIL;
653 }
654
655 std::function<void(const std::unique_ptr<LIBRARY_TABLE>&, bool parentHidden)> processTable =
656 [&]( const std::unique_ptr<LIBRARY_TABLE>& aTable, const bool parentHidden )
657 {
658 if( aTable->Type() != aType )
659 return;
660
661 if( aTable->IsOk() || aIncludeInvalid )
662 {
663 for( LIBRARY_TABLE_ROW& row : aTable->Rows() )
664 {
665 if( row.IsOk() || aIncludeInvalid )
666 {
667 // Hide child row if parent is hidden
668 if( parentHidden )
669 row.SetHidden( true );
670
671 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
672 {
673 if( !m_childTables.contains( row.URI() ) )
674 continue;
675
676 // Don't include libraries from disabled nested tables
677 if( row.Disabled() )
678 continue;
679
680 processTable( m_childTables.at( row.URI() ), row.Hidden() );
681 }
682 else
683 {
684 if( !rows.contains( row.Nickname() ) )
685 rowOrder.emplace_back( row.Nickname() );
686
687 rows[ row.Nickname() ] = &row;
688 }
689 }
690 }
691 }
692 };
693
694 for( const std::unique_ptr<LIBRARY_TABLE>& table :
695 std::views::join( tables ) | std::views::values )
696 {
697 processTable( table, false );
698 }
699
700 std::vector<LIBRARY_TABLE_ROW*> ret;
701
702 for( const wxString& row : rowOrder )
703 ret.emplace_back( rows[row] );
704
705 return ret;
706}
707
708
709std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::GetRow( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
710 LIBRARY_TABLE_SCOPE aScope )
711{
712 {
713 std::lock_guard lock( m_rowCacheMutex );
714 auto key = std::make_tuple( aType, aScope, aNickname );
715
716 if( auto it = m_rowCache.find( key ); it != m_rowCache.end() )
717 return it->second;
718 }
719
720 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
721 {
722 if( row->Nickname() == aNickname )
723 {
724 std::lock_guard lock( m_rowCacheMutex );
725 m_rowCache[std::make_tuple( aType, aScope, aNickname )] = row;
726 return row;
727 }
728 }
729
730 return std::nullopt;
731}
732
733
734std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::FindRowByURI( LIBRARY_TABLE_TYPE aType,
735 const wxString& aUri,
736 LIBRARY_TABLE_SCOPE aScope ) const
737{
738 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
739 {
740 if( UrisAreEquivalent( GetFullURI( row, true ), aUri ) )
741 return row;
742 }
743
744 return std::nullopt;
745}
746
747
748void LIBRARY_MANAGER::ReloadLibraryEntry( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
749 LIBRARY_TABLE_SCOPE aScope )
750{
751 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
752 ( *adapter )->ReloadLibraryEntry( aNickname, aScope );
753}
754
755
757 const wxString& aNickname )
758{
759 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
760 return ( *adapter )->LoadLibraryEntry( aNickname );
761
762 return std::nullopt;
763}
764
765
766void LIBRARY_MANAGER::LoadProjectTables( const wxString& aProjectPath,
767 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
768{
769 if( wxFileName::IsDirReadable( aProjectPath ) )
770 {
771 loadTables( aProjectPath, LIBRARY_TABLE_SCOPE::PROJECT, aTablesToLoad );
772 }
773 else
774 {
775 m_projectTables.clear();
776 wxLogTrace( traceLibraries, "New project path %s is not readable, not loading project tables", aProjectPath );
777 }
778}
779
780
782 std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
783{
784 if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
785 {
787 LoadProjectTables( aTablesToLoad );
788 }
789 else
790 {
791 LoadGlobalTables( aTablesToLoad );
792 }
793}
794
795
796std::optional<wxString> LIBRARY_MANAGER::GetFullURI( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
797 bool aSubstituted )
798{
799 if( std::optional<const LIBRARY_TABLE_ROW*> result = GetRow( aType, aNickname ) )
800 return GetFullURI( *result, aSubstituted );
801
802 return std::nullopt;
803}
804
805
806wxString LIBRARY_MANAGER::GetFullURI( const LIBRARY_TABLE_ROW* aRow, bool aSubstituted )
807{
808 if( aSubstituted )
809 return ExpandEnvVarSubstitutions( aRow->URI(), &Pgm().GetSettingsManager().Prj() );
810
811 return aRow->URI();
812}
813
814
815wxString LIBRARY_MANAGER::ExpandURI( const wxString& aShortURI, const PROJECT& aProject )
816{
817 wxLogNull doNotLog; // We do our own error reporting; we don't want to hear about missing envvars
818
819 wxFileName path( ExpandEnvVarSubstitutions( aShortURI, &aProject ) );
820 path.MakeAbsolute();
821 return path.GetFullPath();
822}
823
824
825bool LIBRARY_MANAGER::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 )
826{
827 // Avoid comparing filenames as wxURIs
828 if( aURI1.Find( "://" ) != wxNOT_FOUND )
829 {
830 // found as full path
831 return aURI1 == aURI2;
832 }
833 else
834 {
835 const wxFileName fn1( aURI1 );
836 const wxFileName fn2( aURI2 );
837
838 // This will also test if the file is a symlink so if we are comparing
839 // a symlink to the same real file, the comparison will be true. See
840 // wxFileName::SameAs() in the wxWidgets source.
841
842 // found as full path and file name
843 return fn1 == fn2;
844 }
845}
846
847
851
852
857
858
862
863
868
869
871{
872 abortLoad();
873
874 {
875 std::unique_lock lock( m_librariesMutex );
876 m_libraries.clear();
877 }
878}
879
880
885
886
887void LIBRARY_MANAGER_ADAPTER::GlobalTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
888{
889 bool me = aChangedTables.size() == 0;
890
891 for( LIBRARY_TABLE_TYPE type : aChangedTables )
892 {
893 if( type == Type() )
894 {
895 me = true;
896 break;
897 }
898 }
899
900 if( !me )
901 return;
902
903 abortLoad();
904
905 {
906 std::unique_lock lock( globalLibsMutex() );
907 globalLibs().clear();
908 }
909}
910
911
913{
914 // Testing is expensive; skip it if we already have a library with the same
915 // nickname and URI as the row under test
916 if( std::optional<LIB_DATA*> libData = fetchIfLoaded( aRow.Nickname() ) )
917 {
918 const LIBRARY_TABLE_ROW* loadedRow = ( *libData )->row;
919
920 if( loadedRow->URI() == aRow.URI() && loadedRow->Type() == aRow.Type() )
921 {
922 aRow.SetOk( loadedRow->IsOk() );
923 return;
924 }
925 }
926
927 abortLoad();
928
930
931 if( plugin.has_value() )
932 {
933 LIB_DATA lib;
934 lib.row = &aRow;
935 lib.plugin.reset( *plugin );
936
937 std::optional<LIB_STATUS> status = LoadOne( &lib );
938
939 if( status.has_value() )
940 {
941 aRow.SetOk( status.value().load_status == LOAD_STATUS::LOADED );
942
943 if( status.value().error.has_value() )
944 aRow.SetErrorDescription( status.value().error.value().message );
945 }
946 }
947 else if( plugin.error().message == LIBRARY_TABLE_OK().message )
948 {
949 aRow.SetOk( true );
950 aRow.SetErrorDescription( wxEmptyString );
951 }
952 else
953 {
954 aRow.SetOk( false );
955 aRow.SetErrorDescription( plugin.error().message );
956 }
957}
958
959
960
962{
963 wxCHECK( m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL ), nullptr );
964 return *m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL );
965}
966
967
968std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER_ADAPTER::ProjectTable() const
969{
971}
972
973
974std::optional<wxString> LIBRARY_MANAGER_ADAPTER::FindLibraryByURI( const wxString& aURI ) const
975{
976 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
977 {
978 if( LIBRARY_MANAGER::UrisAreEquivalent( row->URI(), aURI ) )
979 return row->Nickname();
980 }
981
982 return std::nullopt;
983}
984
985
986std::vector<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryNames() const
987{
988 std::vector<wxString> ret;
989 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
990
991 wxLogTrace( traceLibraries, "GetLibraryNames: checking %zu rows from table", rows.size() );
992
993 for( const LIBRARY_TABLE_ROW* row : rows )
994 {
995 wxString nickname = row->Nickname();
996 std::optional<const LIB_DATA*> loaded = fetchIfLoaded( nickname );
997
998 if( loaded )
999 ret.emplace_back( nickname );
1000 }
1001
1002 wxLogTrace( traceLibraries, "GetLibraryNames: returning %zu of %zu libraries", ret.size(), rows.size() );
1003 return ret;
1004}
1005
1006
1007bool LIBRARY_MANAGER_ADAPTER::HasLibrary( const wxString& aNickname, bool aCheckEnabled ) const
1008{
1009 std::optional<const LIB_DATA*> r = fetchIfLoaded( aNickname );
1010
1011 if( r.has_value() )
1012 return !aCheckEnabled || !r.value()->row->Disabled();
1013
1014 return false;
1015}
1016
1017
1018bool LIBRARY_MANAGER_ADAPTER::DeleteLibrary( const wxString& aNickname )
1019{
1020 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1021 {
1022 LIB_DATA* data = *result;
1023 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1024
1025 try
1026 {
1027 return data->plugin->DeleteLibrary( getUri( data->row ), &options );
1028 }
1029 catch( ... )
1030 {
1031 return false;
1032 }
1033 }
1034
1035 return false;
1036}
1037
1038
1039std::optional<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryDescription( const wxString& aNickname ) const
1040{
1041 if( std::optional<const LIB_DATA*> optRow = fetchIfLoaded( aNickname ); optRow )
1042 return ( *optRow )->row->Description();
1043
1044 return std::nullopt;
1045}
1046
1047
1048std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::Rows( LIBRARY_TABLE_SCOPE aScope,
1049 bool aIncludeInvalid ) const
1050{
1051 return m_manager.Rows( Type(), aScope, aIncludeInvalid );
1052}
1053
1054
1055std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::GetRow( const wxString &aNickname,
1056 LIBRARY_TABLE_SCOPE aScope ) const
1057{
1058 return m_manager.GetRow( Type(), aNickname, aScope );
1059}
1060
1061
1062std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::FindRowByURI(
1063 const wxString& aUri,
1064 LIBRARY_TABLE_SCOPE aScope ) const
1065{
1066 return m_manager.FindRowByURI( Type(), aUri, aScope );
1067}
1068
1069
1071{
1072 {
1073 std::lock_guard lock( m_loadMutex );
1074
1075 if( m_futures.empty() )
1076 return;
1077
1078 wxLogTrace( traceLibraries, "Aborting library load..." );
1079 m_abort.store( true );
1080 }
1081
1083 wxLogTrace( traceLibraries, "Aborted" );
1084
1085 {
1086 std::lock_guard lock( m_loadMutex );
1087 m_abort.store( false );
1088 m_futures.clear();
1089 m_loadTotal.store( 0 );
1090 m_loadCount.store( 0 );
1091 }
1092}
1093
1094
1096{
1097 size_t total = m_loadTotal.load();
1098
1099 if( total == 0 )
1100 return std::nullopt;
1101
1102 size_t loaded = m_loadCount.load();
1103 return loaded / static_cast<float>( total );
1104}
1105
1106
1108{
1109 wxLogTrace( traceLibraries, "BlockUntilLoaded: entry, acquiring m_loadMutex" );
1110 std::unique_lock<std::mutex> asyncLock( m_loadMutex );
1111
1112 wxLogTrace( traceLibraries, "BlockUntilLoaded: waiting on %zu futures", m_futures.size() );
1113
1114 for( const std::future<void>& future : m_futures )
1115 future.wait();
1116
1117 wxLogTrace( traceLibraries, "BlockUntilLoaded: all futures complete, loadCount=%zu, loadTotal=%zu",
1118 m_loadCount.load(), m_loadTotal.load() );
1119}
1120
1121
1122bool LIBRARY_MANAGER_ADAPTER::IsLibraryLoaded( const wxString& aNickname )
1123{
1124 {
1125 std::shared_lock lock( m_librariesMutex );
1126
1127 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1128 return it->second.status.load_status == LOAD_STATUS::LOADED;
1129 }
1130
1131 {
1132 std::shared_lock lock( globalLibsMutex() );
1133
1134 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1135 return it->second.status.load_status == LOAD_STATUS::LOADED;
1136 }
1137
1138 return false;
1139}
1140
1141
1142std::optional<LIBRARY_ERROR> LIBRARY_MANAGER_ADAPTER::LibraryError( const wxString& aNickname ) const
1143{
1144 {
1145 std::shared_lock lock( m_librariesMutex );
1146
1147 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1148 return it->second.status.error;
1149 }
1150
1151 {
1152 std::shared_lock lock( globalLibsMutex() );
1153
1154 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1155 return it->second.status.error;
1156 }
1157
1158 return std::nullopt;
1159}
1160
1161
1162std::vector<std::pair<wxString, LIB_STATUS>> LIBRARY_MANAGER_ADAPTER::GetLibraryStatuses() const
1163{
1164 std::vector<std::pair<wxString, LIB_STATUS>> ret;
1165
1166 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
1167 {
1168 if( row->Disabled() )
1169 continue;
1170
1171 if( std::optional<LIB_STATUS> result = GetLibraryStatus( row->Nickname() ) )
1172 {
1173 ret.emplace_back( std::make_pair( row->Nickname(), *result ) );
1174 }
1175 else
1176 {
1177 // This should probably never happen, but until that can be proved...
1178 ret.emplace_back( std::make_pair( row->Nickname(),
1179 LIB_STATUS( {
1180 .load_status = LOAD_STATUS::LOAD_ERROR,
1181 .error = LIBRARY_ERROR( _( "Library not found in library table" ) )
1182 } ) ) );
1183 }
1184 }
1185
1186 return ret;
1187}
1188
1189
1191{
1192 wxString errors;
1193
1194 for( const auto& [nickname, status] : GetLibraryStatuses() )
1195 {
1196 if( status.load_status == LOAD_STATUS::LOAD_ERROR && status.error )
1197 {
1198 if( !errors.IsEmpty() )
1199 errors += wxS( "\n" );
1200
1201 errors += wxString::Format( _( "Library '%s': %s" ),
1202 nickname, status.error->message );
1203 }
1204 }
1205
1206 return errors;
1207}
1208
1209
1210std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::LoadLibraryEntry( const wxString& aNickname )
1211{
1213
1214 if( result.has_value() )
1215 return LoadOne( *result );
1216
1217 return std::nullopt;
1218}
1219
1220
1222{
1223 auto reloadScope =
1224 [&]( LIBRARY_TABLE_SCOPE aScopeToReload, std::map<wxString, LIB_DATA>& aTarget,
1225 std::shared_mutex& aMutex )
1226 {
1227 bool wasLoaded = false;
1228
1229 {
1230 std::unique_lock lock( aMutex );
1231 auto it = aTarget.find( aNickname );
1232
1233 if( it != aTarget.end() && it->second.plugin )
1234 {
1235 wasLoaded = true;
1236 aTarget.erase( it );
1237 }
1238 }
1239
1240 if( wasLoaded )
1241 {
1242 LIBRARY_RESULT<LIB_DATA*> result = loadFromScope( aNickname, aScopeToReload, aTarget, aMutex );
1243
1244 if( !result.has_value() )
1245 {
1246 wxLogTrace( traceLibraries, "ReloadLibraryEntry: failed to reload %s (%s): %s",
1247 aNickname, magic_enum::enum_name( aScopeToReload ),
1248 result.error().message );
1249 }
1250 }
1251 };
1252
1253 switch( aScope )
1254 {
1257 break;
1258
1261 break;
1262
1267 break;
1268 }
1269}
1270
1271
1272bool LIBRARY_MANAGER_ADAPTER::IsWritable( const wxString& aNickname ) const
1273{
1274 if( std::optional<const LIB_DATA*> result = fetchIfLoaded( aNickname ) )
1275 {
1276 const LIB_DATA* rowData = *result;
1277 return rowData->plugin->IsLibraryWritable( getUri( rowData->row ) );
1278 }
1279
1280 return false;
1281}
1282
1283
1284bool LIBRARY_MANAGER_ADAPTER::CreateLibrary( const wxString& aNickname )
1285{
1286 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1287 {
1288 LIB_DATA* data = *result;
1289 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1290
1291 try
1292 {
1293 data->plugin->CreateLibrary( getUri( data->row ), &options );
1294 return true;
1295 }
1296 catch( ... )
1297 {
1298 return false;
1299 }
1300 }
1301
1302 return false;
1303}
1304
1305
1307{
1308 return LIBRARY_MANAGER::ExpandURI( aRow->URI(), Pgm().GetSettingsManager().Prj() );
1309}
1310
1311
1312std::optional<const LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname ) const
1313{
1314 {
1315 std::shared_lock lock( m_librariesMutex );
1316
1317 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1318 {
1319 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1320 return &it->second;
1321
1322 return std::nullopt;
1323 }
1324 }
1325
1326 {
1327 std::shared_lock lock( globalLibsMutex() );
1328
1329 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1330 {
1331 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1332 return &it->second;
1333
1334 return std::nullopt;
1335 }
1336 }
1337
1338 return std::nullopt;
1339}
1340
1341
1342std::optional<LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded( const wxString& aNickname )
1343{
1344 {
1345 std::shared_lock lock( m_librariesMutex );
1346
1347 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1348 {
1349 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1350 return &it->second;
1351
1352 return std::nullopt;
1353 }
1354 }
1355
1356 {
1357 std::shared_lock lock( globalLibsMutex() );
1358
1359 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1360 {
1361 if( it->second.status.load_status == LOAD_STATUS::LOADED )
1362 return &it->second;
1363
1364 return std::nullopt;
1365 }
1366 }
1367
1368 return std::nullopt;
1369}
1370
1371
1373 LIBRARY_TABLE_SCOPE aScope,
1374 std::map<wxString, LIB_DATA>& aTarget,
1375 std::shared_mutex& aMutex )
1376{
1377 bool present = false;
1378
1379 {
1380 std::shared_lock lock( aMutex );
1381 present = aTarget.contains( aNickname ) && aTarget.at( aNickname ).plugin;
1382 }
1383
1384 if( !present )
1385 {
1386 if( auto result = m_manager.GetRow( Type(), aNickname, aScope ) )
1387 {
1388 const LIBRARY_TABLE_ROW* row = *result;
1389 wxLogTrace( traceLibraries, "Library %s (%s) not yet loaded, will attempt...",
1390 aNickname, magic_enum::enum_name( aScope ) );
1391
1392 if( LIBRARY_RESULT<IO_BASE*> plugin = createPlugin( row ); plugin.has_value() )
1393 {
1394 std::unique_lock lock( aMutex );
1395
1396 aTarget[ row->Nickname() ].status.load_status = LOAD_STATUS::LOADING;
1397 aTarget[ row->Nickname() ].row = row;
1398 aTarget[ row->Nickname() ].plugin.reset( *plugin );
1399
1400 return &aTarget.at( aNickname );
1401 }
1402 else
1403 {
1404 return tl::unexpected( plugin.error() );
1405 }
1406 }
1407
1408 return nullptr;
1409 }
1410
1411 std::shared_lock lock( aMutex );
1412 return &aTarget.at( aNickname );
1413}
1414
1415
1417{
1420
1421 if( !result.has_value() || *result )
1422 return result;
1423
1425
1426 if( !result.has_value() || *result )
1427 return result;
1428
1429 wxString msg = wxString::Format( _( "Library %s not found" ), aNickname );
1430 return tl::unexpected( LIBRARY_ERROR( msg ) );
1431}
1432
1433
1434std::optional<LIB_STATUS> LIBRARY_MANAGER_ADAPTER::GetLibraryStatus( const wxString& aNickname ) const
1435{
1436 {
1437 std::shared_lock lock( m_librariesMutex );
1438
1439 if( auto it = m_libraries.find( aNickname ); it != m_libraries.end() )
1440 return it->second.status;
1441 }
1442
1443 {
1444 std::shared_lock lock( globalLibsMutex() );
1445
1446 if( auto it = globalLibs().find( aNickname ); it != globalLibs().end() )
1447 return it->second.status;
1448 }
1449
1450 return std::nullopt;
1451}
1452
1453
1455{
1456 std::unique_lock<std::mutex> asyncLock( m_loadMutex, std::try_to_lock );
1457
1458 if( !asyncLock )
1459 return;
1460
1461 std::erase_if( m_futures,
1462 []( std::future<void>& aFuture )
1463 {
1464 return aFuture.valid()
1465 && aFuture.wait_for( 0s ) == std::future_status::ready;
1466 } );
1467
1468 if( !m_futures.empty() )
1469 {
1470 wxLogTrace( traceLibraries, "Cannot AsyncLoad, futures from a previous call remain!" );
1471 return;
1472 }
1473
1474 std::vector<LIBRARY_TABLE_ROW*> rows = m_manager.Rows( Type() );
1475
1476 m_loadTotal.store( rows.size() );
1477 m_loadCount.store( 0 );
1478
1479 if( rows.empty() )
1480 {
1481 wxLogTrace( traceLibraries, "AsyncLoad: no libraries left to load; exiting" );
1482 return;
1483 }
1484
1486
1487 auto check =
1488 [&]( const wxString& aLib, std::map<wxString, LIB_DATA>& aMap, std::shared_mutex& aMutex )
1489 {
1490 std::shared_lock lock( aMutex );
1491
1492 if( auto it = aMap.find( aLib ); it != aMap.end() )
1493 {
1494 LOAD_STATUS status = it->second.status.load_status;
1495
1496 if( status == LOAD_STATUS::LOADED || status == LOAD_STATUS::LOADING )
1497 return true;
1498 }
1499
1500 return false;
1501 };
1502
1503 // Collect work items with pre-resolved URIs. URI expansion accesses PROJECT data
1504 // (text variables, env vars) that is not thread-safe, so resolve on the calling thread.
1505 struct LOAD_WORK
1506 {
1507 wxString nickname;
1508 LIBRARY_TABLE_SCOPE scope;
1509 wxString uri;
1510 };
1511
1512 auto workQueue = std::make_shared<std::vector<LOAD_WORK>>();
1513 workQueue->reserve( rows.size() );
1514
1515 for( const LIBRARY_TABLE_ROW* row : rows )
1516 {
1517 wxString nickname = row->Nickname();
1518 LIBRARY_TABLE_SCOPE scope = row->Scope();
1519
1520 if( check( nickname, m_libraries, m_librariesMutex ) )
1521 {
1522 m_loadTotal.fetch_sub( 1 );
1523 continue;
1524 }
1525
1526 if( check( nickname, globalLibs(), globalLibsMutex() ) )
1527 {
1528 m_loadTotal.fetch_sub( 1 );
1529 continue;
1530 }
1531
1532 workQueue->emplace_back( LOAD_WORK{ nickname, scope, getUri( row ) } );
1533 }
1534
1535 if( workQueue->empty() )
1536 {
1537 wxLogTrace( traceLibraries, "AsyncLoad: all libraries already loaded; exiting" );
1538 return;
1539 }
1540
1541 // Cap loading threads to leave headroom for the GUI and other thread pool work.
1542 // Each worker pulls libraries from a shared queue, so we submit fewer tasks than
1543 // libraries and avoid flooding the pool.
1544 size_t poolSize = tp.get_thread_count();
1545 size_t maxLoadThreads = std::max<size_t>( 1, poolSize > 2 ? poolSize - 2 : 1 );
1546 size_t numWorkers = std::min( maxLoadThreads, workQueue->size() );
1547
1548 auto workIndex = std::make_shared<std::atomic<size_t>>( 0 );
1549
1550 wxLogTrace( traceLibraries, "AsyncLoad: %zu libraries to load, using %zu worker threads (pool has %zu)",
1551 workQueue->size(), numWorkers, poolSize );
1552
1553 for( size_t w = 0; w < numWorkers; ++w )
1554 {
1555 m_futures.emplace_back( tp.submit_task(
1556 [this, workQueue, workIndex]()
1557 {
1558 while( true )
1559 {
1560 if( m_abort.load() )
1561 return;
1562
1563 size_t idx = workIndex->fetch_add( 1 );
1564
1565 if( idx >= workQueue->size() )
1566 return;
1567
1568 const LOAD_WORK& work = ( *workQueue )[idx];
1569 LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( work.nickname );
1570
1571 if( result.has_value() )
1572 {
1573 LIB_DATA* lib = *result;
1574
1575 try
1576 {
1577 {
1578 std::unique_lock lock(
1579 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1580 ? globalLibsMutex()
1581 : m_librariesMutex );
1582 lib->status.load_status = LOAD_STATUS::LOADING;
1583 }
1584
1585 enumerateLibrary( lib, work.uri );
1586
1587 {
1588 std::unique_lock lock(
1589 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1590 ? globalLibsMutex()
1591 : m_librariesMutex );
1592 lib->status.load_status = LOAD_STATUS::LOADED;
1593 }
1594 }
1595 catch( IO_ERROR& e )
1596 {
1597 std::unique_lock lock(
1598 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1599 ? globalLibsMutex()
1600 : m_librariesMutex );
1601 lib->status.load_status = LOAD_STATUS::LOAD_ERROR;
1602 lib->status.error = LIBRARY_ERROR( { e.What() } );
1603 wxLogTrace( traceLibraries, "%s: plugin threw exception: %s",
1604 work.nickname, e.What() );
1605 }
1606 }
1607 else
1608 {
1609 std::unique_lock lock(
1610 work.scope == LIBRARY_TABLE_SCOPE::GLOBAL
1611 ? globalLibsMutex()
1612 : m_librariesMutex );
1613
1614 std::map<wxString, LIB_DATA>& target =
1615 ( work.scope == LIBRARY_TABLE_SCOPE::GLOBAL ) ? globalLibs()
1616 : m_libraries;
1617
1618 target[work.nickname].status = LIB_STATUS( {
1619 .load_status = LOAD_STATUS::LOAD_ERROR,
1620 .error = result.error()
1621 } );
1622 }
1623
1624 ++m_loadCount;
1625 }
1626 }, BS::pr::lowest ) );
1627 }
1628
1629 wxLogTrace( traceLibraries, "Started async load of %zu libraries", workQueue->size() );
1630}
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
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.
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
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)
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
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:558
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
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