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 <common.h>
22#include <env_vars.h>
23#include <list>
24#include <magic_enum.hpp>
25#include <unordered_set>
26
27#include <paths.h>
28#include <pgm_base.h>
29#include <richio.h>
30#include <trace_helpers.h>
32
36#include <wx/dir.h>
37#include <wx/log.h>
38
40{
41 std::vector<LIBRARY_TABLE> tables;
42};
43
44
48
49
51
52
53void LIBRARY_MANAGER::loadTables( const wxString& aTablePath, LIBRARY_TABLE_SCOPE aScope,
54 std::vector<LIBRARY_TABLE_TYPE> aTablesToLoad )
55{
56 auto getTarget =
57 [&]() -> std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>&
58 {
59 switch( aScope )
60 {
62 return m_tables;
63
65 return m_projectTables;
66
67 default:
68 wxCHECK_MSG( false, m_tables, "Invalid scope passed to loadTables" );
69 }
70 };
71
72 std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>& aTarget = getTarget();
73
74 if( aTablesToLoad.size() == 0 )
76
77 for( LIBRARY_TABLE_TYPE type : aTablesToLoad )
78 {
79 aTarget.erase( type );
80
81 wxFileName fn( aTablePath, tableFileName( type ) );
82
83 if( fn.IsFileReadable() )
84 {
85 auto table = std::make_unique<LIBRARY_TABLE>( fn, aScope );
86 wxCHECK2( table->Type() == type, continue );
87 aTarget[type] = std::move( table );
88 loadNestedTables( *aTarget[type] );
89 }
90 else
91 {
92 wxLogTrace( traceLibraries, "No library table found at %s", fn.GetFullPath() );
93 }
94 }
95}
96
97
99{
100 std::unordered_set<wxString> seenTables;
101
102 std::function<void(LIBRARY_TABLE&)> processOneTable =
103 [&]( LIBRARY_TABLE& aTable )
104 {
105 seenTables.insert( aTable.Path() );
106
107 if( !aTable.IsOk() )
108 return;
109
110 for( LIBRARY_TABLE_ROW& row : aTable.Rows() )
111 {
112 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
113 {
114 wxFileName file( ExpandURI( row.URI(), Pgm().GetSettingsManager().Prj() ) );
115
116 // URI may be relative to parent
117 file.MakeAbsolute( wxFileName( aTable.Path() ).GetPath() );
118
120 wxString src = file.GetFullPath();
121
122 if( seenTables.contains( src ) )
123 {
124 wxLogTrace( traceLibraries, "Library table %s has already been loaded!", src );
125 row.SetOk( false );
126 row.SetErrorDescription( _( "A reference to this library table already exists" ) );
127 continue;
128 }
129
130 auto child = std::make_unique<LIBRARY_TABLE>( file, aRootTable.Scope() );
131
132 processOneTable( *child );
133
134 if( !child->IsOk() )
135 {
136 row.SetOk( false );
137 row.SetErrorDescription( child->ErrorDescription() );
138 }
139
140 m_childTables.insert( { row.URI(), std::move( child ) } );
141 }
142 }
143 };
144
145 processOneTable( aRootTable );
146}
147
148
150{
151 switch( aType )
152 {
156 default: wxCHECK( false, wxEmptyString );
157 }
158}
159
160
162{
163 if( aScope == LIBRARY_TABLE_SCOPE::GLOBAL )
164 {
165 wxCHECK( !m_tables.contains( aType ), /* void */ );
166 wxFileName fn( PATHS::GetUserSettingsPath(), tableFileName( aType ) );
167
168 m_tables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::GLOBAL );
169 m_tables[aType]->SetType( aType );
170 }
171 else if( aScope == LIBRARY_TABLE_SCOPE::PROJECT )
172 {
173 wxCHECK( !m_projectTables.contains( aType ), /* void */ );
174 wxFileName fn( Pgm().GetSettingsManager().Prj().GetProjectDirectory(), tableFileName( aType ) );
175
176 m_projectTables[aType] = std::make_unique<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::PROJECT );
177 m_projectTables[aType]->SetType( aType );
178 }
179}
180
181
182class PCM_LIB_TRAVERSER final : public wxDirTraverser
183{
184public:
185 explicit PCM_LIB_TRAVERSER( const wxString& aBasePath, LIBRARY_MANAGER& aManager,
186 const wxString& aPrefix ) :
187 m_manager( aManager ),
189 m_path_prefix( aBasePath ),
190 m_lib_prefix( aPrefix )
191 {
192 wxFileName f( aBasePath, "" );
193 m_prefix_dir_count = f.GetDirCount();
194
196 .value_or( nullptr );
198 .value_or( nullptr );
200 .value_or( nullptr );
201 }
202
204 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
205 {
206 wxFileName file = wxFileName::FileName( aFilePath );
207
208 // consider a file to be a lib if it's name ends with .kicad_sym and
209 // it is under $KICADn_3RD_PARTY/symbols/<pkgid>/ i.e. has nested level of at least +2
210 if( file.GetExt() == wxT( "kicad_sym" ) && file.GetDirCount() >= m_prefix_dir_count + 2
211 && file.GetDirs()[m_prefix_dir_count] == wxT( "symbols" ) )
212 {
214 }
215
216 return wxDIR_CONTINUE;
217 }
218
220 wxDirTraverseResult OnDir( const wxString& dirPath ) override
221 {
222 static wxString designBlockExt = wxString::Format( wxS( ".%s" ),
224 wxFileName dir = wxFileName::DirName( dirPath );
225
226 // consider a directory to be a lib if it's name ends with .pretty and
227 // it is under $KICADn_3RD_PARTY/footprints/<pkgid>/ i.e. has nested level of at least +3
228 if( dirPath.EndsWith( wxS( ".pretty" ) ) && dir.GetDirCount() >= m_prefix_dir_count + 3
229 && dir.GetDirs()[m_prefix_dir_count] == wxT( "footprints" ) )
230 {
232 }
233 else if( dirPath.EndsWith( designBlockExt )
234 && dir.GetDirCount() >= m_prefix_dir_count + 3
235 && dir.GetDirs()[m_prefix_dir_count] == wxT( "design_blocks" ) )
236 {
237 addRowIfNecessary( m_designBlockTable, dir, ADD_MODE::AM_DIRECTORY, designBlockExt.Len() );
238 }
239
240 return wxDIR_CONTINUE;
241 }
242
243 std::set<LIBRARY_TABLE*> Modified() const { return m_modified; }
244
245private:
246 void ensureUnique( LIBRARY_TABLE* aTable, const wxString& aBaseName, wxString& aNickname ) const
247 {
248 if( aTable->HasRow( aNickname ) )
249 {
250 int increment = 1;
251
252 do
253 {
254 aNickname = wxString::Format( "%s%s_%d", m_lib_prefix, aBaseName, increment );
255 increment++;
256 } while( aTable->HasRow( aNickname ) );
257 }
258 }
259
260 enum class ADD_MODE
261 {
264 };
265
266 void addRowIfNecessary( LIBRARY_TABLE* aTable, const wxFileName& aSource, ADD_MODE aMode,
267 int aExtensionLength )
268 {
269 wxString versionedPath = wxString::Format( wxS( "${%s}" ),
270 ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
271
272 wxArrayString parts = aSource.GetDirs();
273 parts.RemoveAt( 0, m_prefix_dir_count );
274 parts.Insert( versionedPath, 0 );
275
276 if( aMode == ADD_MODE::AM_FILE )
277 parts.Add( aSource.GetFullName() );
278
279 wxString libPath = wxJoin( parts, '/' );
280
281 if( !aTable->HasRowWithURI( libPath, m_project ) )
282 {
283 wxString name = parts.Last().substr( 0, parts.Last().length() - aExtensionLength );
284 wxString nickname = wxString::Format( "%s%s", m_lib_prefix, name );
285
286 ensureUnique( aTable, name, nickname );
287
288 wxLogTrace( traceLibraries, "Manager: Adding PCM lib '%s' as '%s'", libPath, nickname );
289
290 LIBRARY_TABLE_ROW& row = aTable->InsertRow();
291
292 row.SetNickname( nickname );
293 row.SetURI( libPath );
294 row.SetType( wxT( "KiCad" ) );
295 row.SetDescription( _( "Added by Plugin and Content Manager" ) );
296 m_modified.insert( aTable );
297 }
298 else
299 {
300 wxLogTrace( traceLibraries, "Manager: Not adding existing PCM lib '%s'", libPath );
301 }
302 }
303
307 wxString m_lib_prefix;
309 std::set<LIBRARY_TABLE*> m_modified;
310
314};
315
316
318{
319 wxString basePath = PATHS::GetUserSettingsPath();
320
321 wxFileName fn( basePath, tableFileName( aType ) );
322 fn.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
323
324 return fn.GetFullPath();
325}
326
327
328bool LIBRARY_MANAGER::IsTableValid( const wxString& aPath )
329{
330 if( wxFileName fn( aPath ); fn.IsFileReadable() )
331 {
332 if( LIBRARY_TABLE temp( fn, LIBRARY_TABLE_SCOPE::GLOBAL ); temp.IsOk() )
333 return true;
334 }
335
336 return false;
337}
338
339
341{
342 return InvalidGlobalTables().empty();
343}
344
345
346std::vector<LIBRARY_TABLE_TYPE> LIBRARY_MANAGER::InvalidGlobalTables()
347{
348 std::vector<LIBRARY_TABLE_TYPE> invalidTables;
349 wxString basePath = PATHS::GetUserSettingsPath();
350
354 {
355 if( wxFileName fn( basePath, tableFileName( tableType ) ); !IsTableValid( fn.GetFullPath() ) )
356 invalidTables.emplace_back( tableType );
357 }
358
359 return invalidTables;
360}
361
362
363bool LIBRARY_MANAGER::CreateGlobalTable( LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries )
364{
365 wxFileName fn( DefaultGlobalTablePath( aType ) );
366
368 table.SetType( aType );
369 table.Rows().clear();
370
371 wxFileName defaultLib( PATHS::GetStockTemplatesPath(), tableFileName( aType ) );
372
373 if( !defaultLib.IsFileReadable() )
374 {
375 wxLogTrace( traceLibraries, "Warning: couldn't read default library table for %s at '%s'",
376 magic_enum::enum_name( aType ), defaultLib.GetFullPath() );
377 }
378
379 if( aPopulateDefaultLibraries )
380 {
381 LIBRARY_TABLE_ROW& chained = table.InsertRow();
383 chained.SetNickname( wxT( "KiCad" ) );
384 chained.SetDescription( _( "KiCad Default Libraries" ) );
385 chained.SetURI( defaultLib.GetFullPath() );
386 }
387
388 try
389 {
390 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( fn.GetFullPath(), KICAD_FORMAT::FORMAT_MODE::LIBRARY_TABLE );
391 table.Format( &formatter );
392 }
393 catch( IO_ERROR& e )
394 {
395 wxLogTrace( traceLibraries, "Exception while saving: %s", e.What() );
396 return false;
397 }
398
399 return true;
400}
401
402
403void LIBRARY_MANAGER::LoadGlobalTables( std::initializer_list<LIBRARY_TABLE_TYPE> aTablesToLoad )
404{
405 // Cancel any in-progress load
406 {
407 std::scoped_lock lock( m_adaptersMutex );
408
409 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
410 adapter->GlobalTablesChanged( aTablesToLoad );
411 }
412
414
416 KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>( "kicad" );
417
418 wxCHECK( settings, /* void */ );
419
420 const ENV_VAR_MAP& vars = Pgm().GetLocalEnvVariables();
421 std::optional<wxString> packagesPath = ENV_VAR::GetVersionedEnvVarValue( vars, wxT( "3RD_PARTY" ) );
422
423 if( packagesPath && settings->m_PcmLibAutoAdd )
424 {
425 // Scan for libraries in PCM packages directory
426 wxFileName d( *packagesPath, "" );
427
428 if( d.DirExists() )
429 {
430 PCM_LIB_TRAVERSER traverser( *packagesPath, *this, settings->m_PcmLibPrefix );
431 wxDir dir( d.GetPath() );
432
433 dir.Traverse( traverser );
434
435 for( LIBRARY_TABLE* table : traverser.Modified() )
436 {
437 table->Save().map_error(
438 []( const LIBRARY_ERROR& aError )
439 {
440 wxLogTrace( traceLibraries, "Warning: save failed after PCM auto-add: %s",
441 aError.message );
442 } );
443 }
444 }
445 }
446
447 auto cleanupRemovedPCMLibraries =
448 [&]( LIBRARY_TABLE_TYPE aType )
449 {
450 LIBRARY_TABLE* table = Table( aType, LIBRARY_TABLE_SCOPE::GLOBAL ).value_or( nullptr );
451 wxCHECK( table, /* void */ );
452
453 auto toErase = std::ranges::remove_if( table->Rows(),
454 [&]( const LIBRARY_TABLE_ROW& aRow )
455 {
456 wxString path = GetFullURI( &aRow, true );
457 return path.StartsWith( *packagesPath ) && !wxFile::Exists( path );
458 } );
459
460 table->Rows().erase( toErase.begin(), toErase.end() );
461
462 if( !toErase.empty() )
463 {
464 table->Save().map_error(
465 []( const LIBRARY_ERROR& aError )
466 {
467 wxLogTrace( traceLibraries,
468 "Warning: save failed after PCM auto-remove: %s",
469 aError.message );
470 } );
471 }
472 };
473
474 if( packagesPath && settings->m_PcmLibAutoRemove )
475 {
476 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::SYMBOL );
477 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::FOOTPRINT );
478 cleanupRemovedPCMLibraries( LIBRARY_TABLE_TYPE::DESIGN_BLOCK );
479 }
480}
481
482
484{
485 LoadProjectTables( Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
486
487 std::scoped_lock lock( m_adaptersMutex );
488
489 for( const std::unique_ptr<LIBRARY_MANAGER_ADAPTER>& adapter : m_adapters | std::views::values )
490 adapter->ProjectChanged();
491}
492
493
495 std::unique_ptr<LIBRARY_MANAGER_ADAPTER>&& aAdapter )
496{
497 std::scoped_lock lock( m_adaptersMutex );
498
499 wxCHECK_MSG( !m_adapters.contains( aType ), /**/, "You should only register an adapter once!" );
500
501 m_adapters[aType] = std::move( aAdapter );
502}
503
504
505std::optional<LIBRARY_MANAGER_ADAPTER*> LIBRARY_MANAGER::Adapter( LIBRARY_TABLE_TYPE aType ) const
506{
507 std::scoped_lock lock( m_adaptersMutex );
508
509 if( m_adapters.contains( aType ) )
510 return m_adapters.at( aType ).get();
511
512 return std::nullopt;
513}
514
515
516std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER::Table( LIBRARY_TABLE_TYPE aType,
517 LIBRARY_TABLE_SCOPE aScope )
518{
519 switch( aScope )
520 {
523 wxCHECK_MSG( false, std::nullopt, "Table() requires a single scope" );
524
526 {
527 if( !m_tables.contains( aType ) )
528 {
529 wxLogTrace( traceLibraries, "WARNING: missing global table (%s)",
530 magic_enum::enum_name( aType ) );
531 return std::nullopt;
532 }
533
534 return m_tables.at( aType ).get();
535 }
536
538 {
539 // TODO: handle multiple projects
540 if( !m_projectTables.contains( aType ) )
541 {
542 if( !Pgm().GetSettingsManager().Prj().IsNullProject() )
544 else
545 return std::nullopt;
546 }
547
548 return m_projectTables.at( aType ).get();
549 }
550 }
551
552 return std::nullopt;
553}
554
555
556std::vector<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::Rows( LIBRARY_TABLE_TYPE aType,
557 LIBRARY_TABLE_SCOPE aScope,
558 bool aIncludeInvalid ) const
559{
560 std::map<wxString, LIBRARY_TABLE_ROW*> rows;
561 std::vector<wxString> rowOrder;
562
563 std::list<std::ranges::ref_view<
564 const std::map<LIBRARY_TABLE_TYPE, std::unique_ptr<LIBRARY_TABLE>>
565 >> tables;
566
567 switch( aScope )
568 {
570 tables = { std::views::all( m_tables ) };
571 break;
572
574 tables = { std::views::all( m_projectTables ) };
575 break;
576
578 tables = { std::views::all( m_tables ), std::views::all( m_projectTables ) };
579 break;
580
582 wxFAIL;
583 }
584
585 std::function<void(const std::unique_ptr<LIBRARY_TABLE>&)> processTable =
586 [&]( const std::unique_ptr<LIBRARY_TABLE>& aTable )
587 {
588 if( aTable->Type() != aType )
589 return;
590
591 if( aTable->IsOk() || aIncludeInvalid )
592 {
593 for( LIBRARY_TABLE_ROW& row : aTable->Rows() )
594 {
595 if( row.IsOk() || aIncludeInvalid )
596 {
597 if( row.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
598 {
599 if( !m_childTables.contains( row.URI() ) )
600 continue;
601
602 processTable( m_childTables.at( row.URI() ) );
603 }
604 else
605 {
606 if( !rows.contains( row.Nickname() ) )
607 rowOrder.emplace_back( row.Nickname() );
608
609 rows[ row.Nickname() ] = &row;
610 }
611 }
612 }
613 }
614 };
615
616 for( const std::unique_ptr<LIBRARY_TABLE>& table :
617 std::views::join( tables ) | std::views::values )
618 {
619 processTable( table );
620 }
621
622 std::vector<LIBRARY_TABLE_ROW*> ret;
623
624 for( const wxString& row : rowOrder )
625 ret.emplace_back( rows[row] );
626
627 return ret;
628}
629
630
631std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::GetRow( LIBRARY_TABLE_TYPE aType,
632 const wxString& aNickname,
633 LIBRARY_TABLE_SCOPE aScope ) const
634{
635 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
636 {
637 if( row->Nickname() == aNickname )
638 return row;
639 }
640
641 return std::nullopt;
642}
643
644
645std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER::FindRowByURI( LIBRARY_TABLE_TYPE aType,
646 const wxString& aUri,
647 LIBRARY_TABLE_SCOPE aScope ) const
648{
649 for( LIBRARY_TABLE_ROW* row : Rows( aType, aScope, true ) )
650 {
651 if( UrisAreEquivalent( GetFullURI( row, true ), aUri ) )
652 return row;
653 }
654
655 return std::nullopt;
656}
657
658
659void LIBRARY_MANAGER::ReloadLibraryEntry( LIBRARY_TABLE_TYPE aType, const wxString& aNickname,
660 LIBRARY_TABLE_SCOPE aScope )
661{
662 if( std::optional<LIBRARY_MANAGER_ADAPTER*> adapter = Adapter( aType ); adapter )
663 ( *adapter )->ReloadLibraryEntry( aNickname, aScope );
664}
665
666
667void LIBRARY_MANAGER::LoadProjectTables( const wxString& aProjectPath )
668{
669 if( wxFileName::IsDirReadable( aProjectPath ) )
670 {
672 }
673 else
674 {
675 m_projectTables.clear();
676 wxLogTrace( traceLibraries,
677 "New project path %s is not readable, not loading project tables",
678 aProjectPath );
679 }
680}
681
682
683std::optional<wxString> LIBRARY_MANAGER::GetFullURI( LIBRARY_TABLE_TYPE aType,
684 const wxString& aNickname,
685 bool aSubstituted ) const
686{
687 if( std::optional<const LIBRARY_TABLE_ROW*> result = GetRow( aType, aNickname ) )
688 return GetFullURI( *result, aSubstituted );
689
690 return std::nullopt;
691}
692
693
695 bool aSubstituted )
696{
697 if( aSubstituted )
698 return ExpandEnvVarSubstitutions( aRow->URI(), nullptr );
699
700 return aRow->URI();
701}
702
703
704wxString LIBRARY_MANAGER::ExpandURI( const wxString& aShortURI, const PROJECT& aProject )
705{
706 wxFileName path( ExpandEnvVarSubstitutions( aShortURI, &aProject ) );
707 path.MakeAbsolute();
708 return path.GetFullPath();
709}
710
711
712bool LIBRARY_MANAGER::UrisAreEquivalent( const wxString& aURI1, const wxString& aURI2 )
713{
714 // Avoid comparing filenames as wxURIs
715 if( aURI1.Find( "://" ) != wxNOT_FOUND )
716 {
717 // found as full path
718 return aURI1 == aURI2;
719 }
720 else
721 {
722 const wxFileName fn1( aURI1 );
723 const wxFileName fn2( aURI2 );
724
725 // This will also test if the file is a symlink so if we are comparing
726 // a symlink to the same real file, the comparison will be true. See
727 // wxFileName::SameAs() in the wxWidgets source.
728
729 // found as full path and file name
730 return fn1 == fn2;
731 }
732}
733
734
738
739
744
745
749
750
755
756
758{
759 abortLoad();
760
761 {
762 std::lock_guard lock( m_libraries_mutex );
763 m_libraries.clear();
764 }
765}
766
767
768void LIBRARY_MANAGER_ADAPTER::GlobalTablesChanged( std::initializer_list<LIBRARY_TABLE_TYPE> aChangedTables )
769{
770 bool me = aChangedTables.size() == 0;
771
772 for( LIBRARY_TABLE_TYPE type : aChangedTables )
773 {
774 if( type == Type() )
775 {
776 me = true;
777 break;
778 }
779 }
780
781 if( !me )
782 return;
783
784 abortLoad();
785
786 {
787 std::lock_guard lock( globalLibsMutex() );
788 globalLibs().clear();
789 }
790}
791
792
794{
795 abortLoad();
796
798
799 if( plugin.has_value() )
800 {
801 LIB_DATA lib;
802 lib.row = &aRow;
803 lib.plugin.reset( *plugin );
804
805 std::optional<LIB_STATUS> status = LoadOne( &lib );
806
807 if( status.has_value() )
808 {
809 aRow.SetOk( status.value().load_status == LOAD_STATUS::LOADED );
810
811 if( status.value().error.has_value() )
812 aRow.SetErrorDescription( status.value().error.value().message );
813 }
814 }
815 else
816 {
817 aRow.SetOk( false );
818 aRow.SetErrorDescription( plugin.error().message );
819 }
820}
821
822
823
825{
826 wxCHECK( m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL ), nullptr );
827 return *m_manager.Table( Type(), LIBRARY_TABLE_SCOPE::GLOBAL );
828}
829
830
831std::optional<LIBRARY_TABLE*> LIBRARY_MANAGER_ADAPTER::ProjectTable() const
832{
834}
835
836
837std::optional<wxString> LIBRARY_MANAGER_ADAPTER::FindLibraryByURI( const wxString& aURI ) const
838{
839 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
840 {
841 if( LIBRARY_MANAGER::UrisAreEquivalent( row->URI(), aURI ) )
842 return row->Nickname();
843 }
844
845 return std::nullopt;
846}
847
848
849std::vector<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryNames() const
850{
851 std::vector<wxString> ret;
852
853 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
854 {
855 if( fetchIfLoaded( row->Nickname() ) )
856 ret.emplace_back( row->Nickname() );
857 }
858
859 return ret;
860}
861
862
863bool LIBRARY_MANAGER_ADAPTER::HasLibrary( const wxString& aNickname,
864 bool aCheckEnabled ) const
865{
866 if( std::optional<const LIB_DATA*> r = fetchIfLoaded( aNickname ); r.has_value() )
867 return !aCheckEnabled || !( *r )->row->Disabled();
868
869 return false;
870}
871
872
873bool LIBRARY_MANAGER_ADAPTER::DeleteLibrary( const wxString& aNickname )
874{
875 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
876 {
877 LIB_DATA* data = *result;
878 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
879
880 try
881 {
882 return data->plugin->DeleteLibrary( getUri( data->row ), &options );
883 }
884 catch( ... )
885 {
886 return false;
887 }
888 }
889
890 return false;
891}
892
893
894std::optional<wxString> LIBRARY_MANAGER_ADAPTER::GetLibraryDescription( const wxString& aNickname ) const
895{
896 if( std::optional<const LIB_DATA*> optRow = fetchIfLoaded( aNickname ); optRow )
897 return ( *optRow )->row->Description();
898
899 return std::nullopt;
900}
901
902
903std::vector<LIBRARY_TABLE_ROW *> LIBRARY_MANAGER_ADAPTER::Rows(
904 LIBRARY_TABLE_SCOPE aScope, bool aIncludeInvalid ) const
905{
906 return m_manager.Rows( Type(), aScope, aIncludeInvalid );
907}
908
909
910std::optional<LIBRARY_TABLE_ROW *> LIBRARY_MANAGER_ADAPTER::GetRow(
911 const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope ) const
912{
913 return m_manager.GetRow( Type(), aNickname, aScope );
914}
915
916
917std::optional<LIBRARY_TABLE_ROW*> LIBRARY_MANAGER_ADAPTER::FindRowByURI(
918 const wxString& aUri,
919 LIBRARY_TABLE_SCOPE aScope ) const
920{
921 return m_manager.FindRowByURI( Type(), aUri, aScope );
922}
923
924
926{
927 if( m_futures.empty() )
928 return;
929
930 wxLogTrace( traceLibraries, "Aborting library load..." );
931 m_abort.store( true );
932
934 wxLogTrace( traceLibraries, "Aborted" );
935
936 m_abort.store( false );
937 m_futures.clear();
938 m_loadTotal = 0;
939}
940
941
943{
944 if( m_loadTotal == 0 )
945 return std::nullopt;
946
947 size_t loaded = m_loadCount.load();
948 return loaded / static_cast<float>( m_loadTotal );
949}
950
951
953{
954 for( const std::future<void>& future : m_futures )
955 future.wait();
956}
957
958
959bool LIBRARY_MANAGER_ADAPTER::IsLibraryLoaded( const wxString& aNickname )
960{
961 // TODO: row->IsOK() doesn't actually tell you if a library is loaded
962 // Once we are preloading libraries we can cache the status of plugin load instead
963
964 if( m_libraries.contains( aNickname ) )
965 return m_libraries[aNickname].row->IsOk();
966
967 if( globalLibs().contains( aNickname ) )
968 return globalLibs()[aNickname].row->IsOk();
969
970 return false;
971}
972
973
974std::optional<LIBRARY_ERROR> LIBRARY_MANAGER_ADAPTER::LibraryError( const wxString& aNickname ) const
975{
976 if( m_libraries.contains( aNickname ) )
977 {
978 return m_libraries.at( aNickname ).status.error;
979 }
980
981 if( globalLibs().contains( aNickname ) )
982 {
983 return globalLibs().at( aNickname ).status.error;
984 }
985
986 return std::nullopt;
987}
988
989
990std::vector<std::pair<wxString, LIB_STATUS>> LIBRARY_MANAGER_ADAPTER::GetLibraryStatuses() const
991{
992 std::vector<std::pair<wxString, LIB_STATUS>> ret;
993
994 for( const LIBRARY_TABLE_ROW* row : m_manager.Rows( Type() ) )
995 {
996 if( std::optional<LIB_STATUS> result = GetLibraryStatus( row->Nickname() ) )
997 {
998 ret.emplace_back( std::make_pair( row->Nickname(), *result ) );
999 }
1000 else
1001 {
1002 // This should probably never happen, but until that can be proved...
1003 ret.emplace_back( std::make_pair( row->Nickname(), LIB_STATUS( {
1004 .load_status = LOAD_STATUS::LOAD_ERROR,
1005 .error = LIBRARY_ERROR( _( "Library not found in library table" ) )
1006 } ) ) );
1007 }
1008 }
1009
1010 return ret;
1011}
1012
1013
1014void LIBRARY_MANAGER_ADAPTER::ReloadLibraryEntry( const wxString& aNickname,
1015 LIBRARY_TABLE_SCOPE aScope )
1016{
1017 auto reloadScope =
1018 [&]( LIBRARY_TABLE_SCOPE aScopeToReload, std::map<wxString, LIB_DATA>& aTarget,
1019 std::mutex& aMutex )
1020 {
1021 bool wasLoaded = false;
1022
1023 {
1024 std::lock_guard lock( aMutex );
1025 auto it = aTarget.find( aNickname );
1026
1027 if( it != aTarget.end() && it->second.plugin )
1028 {
1029 wasLoaded = true;
1030 aTarget.erase( it );
1031 }
1032 }
1033
1034 if( wasLoaded )
1035 {
1037 loadFromScope( aNickname, aScopeToReload, aTarget, aMutex );
1038 !result.has_value() )
1039 {
1040 wxLogTrace( traceLibraries,
1041 "ReloadLibraryEntry: failed to reload %s (%s): %s",
1042 aNickname, magic_enum::enum_name( aScopeToReload ),
1043 result.error().message );
1044 }
1045 }
1046 };
1047
1048 switch( aScope )
1049 {
1052 break;
1053
1056 break;
1057
1062 break;
1063 }
1064}
1065
1066
1067bool LIBRARY_MANAGER_ADAPTER::IsWritable( const wxString& aNickname ) const
1068{
1069 if( std::optional<const LIB_DATA*> result = fetchIfLoaded( aNickname ) )
1070 {
1071 const LIB_DATA* rowData = *result;
1072 return rowData->plugin->IsLibraryWritable( getUri( rowData->row ) );
1073 }
1074
1075 return false;
1076}
1077
1078
1079bool LIBRARY_MANAGER_ADAPTER::CreateLibrary( const wxString& aNickname )
1080{
1081 if( LIBRARY_RESULT<LIB_DATA*> result = loadIfNeeded( aNickname ); result.has_value() )
1082 {
1083 LIB_DATA* data = *result;
1084 std::map<std::string, UTF8> options = data->row->GetOptionsMap();
1085
1086 try
1087 {
1088 data->plugin->CreateLibrary( getUri( data->row ), &options );
1089 return true;
1090 }
1091 catch( ... )
1092 {
1093 return false;
1094 }
1095 }
1096
1097 return false;
1098}
1099
1100
1102{
1103 return LIBRARY_MANAGER::ExpandURI( aRow->URI(), Pgm().GetSettingsManager().Prj() );
1104}
1105
1106
1107std::optional<const LIB_DATA*> LIBRARY_MANAGER_ADAPTER::fetchIfLoaded(
1108 const wxString& aNickname ) const
1109{
1110 if( m_libraries.contains( aNickname )
1111 && m_libraries.at( aNickname ).status.load_status == LOAD_STATUS::LOADED )
1112 {
1113 return &m_libraries.at( aNickname );
1114 }
1115
1116 if( globalLibs().contains( aNickname )
1117 && globalLibs().at( aNickname ).status.load_status == LOAD_STATUS::LOADED )
1118 {
1119 return &globalLibs().at( aNickname );
1120 }
1121
1122 return std::nullopt;
1123}
1124
1125
1127 const wxString& aNickname )
1128{
1129 if( m_libraries.contains( aNickname ) && m_libraries.at( aNickname ).status.load_status == LOAD_STATUS::LOADED )
1130 return &m_libraries.at( aNickname );
1131
1132 if( globalLibs().contains( aNickname )
1133 && globalLibs().at( aNickname ).status.load_status == LOAD_STATUS::LOADED )
1134 {
1135 return &globalLibs().at( aNickname );
1136 }
1137
1138 return std::nullopt;
1139}
1140
1141
1143 LIBRARY_TABLE_SCOPE aScope, std::map<wxString, LIB_DATA>& aTarget, std::mutex& aMutex )
1144{
1145 bool present = false;
1146
1147 {
1148 std::lock_guard lock( aMutex );
1149 present = aTarget.contains( aNickname ) && aTarget.at( aNickname ).plugin;
1150 }
1151
1152 if( !present )
1153 {
1154 if( auto result = m_manager.GetRow( Type(), aNickname, aScope ) )
1155 {
1156 const LIBRARY_TABLE_ROW* row = *result;
1157 wxLogTrace( traceLibraries, "Library %s (%s) not yet loaded, will attempt...",
1158 aNickname, magic_enum::enum_name( aScope ) );
1159
1160 if( LIBRARY_RESULT<IO_BASE*> plugin = createPlugin( row ); plugin.has_value() )
1161 {
1162 std::lock_guard lock( aMutex );
1163
1164 aTarget[ row->Nickname() ].status.load_status = LOAD_STATUS::LOADING;
1165 aTarget[ row->Nickname() ].row = row;
1166 aTarget[ row->Nickname() ].plugin.reset( *plugin );
1167
1168 return &aTarget.at( aNickname );
1169 }
1170 else
1171 {
1172 return tl::unexpected( plugin.error() );
1173 }
1174 }
1175
1176 return nullptr;
1177 }
1178
1179 return &aTarget.at( aNickname );
1180}
1181
1182
1184{
1187
1188 if( !result.has_value() || *result )
1189 return result;
1190
1192
1193 if( !result.has_value() || *result )
1194 return result;
1195
1196 wxString msg = wxString::Format( _( "Library %s not found" ), aNickname );
1197 return tl::unexpected( LIBRARY_ERROR( msg ) );
1198}
const char * name
virtual bool DeleteLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Delete an existing library and returns true, or if library does not exist returns false,...
Definition io_base.cpp:53
virtual bool IsLibraryWritable(const wxString &aLibraryPath)
Return true if the library at aLibraryPath is writable.
Definition io_base.cpp:60
virtual void CreateLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr)
Create a new empty library at aLibraryPath empty.
Definition io_base.cpp:46
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
wxString m_PcmLibPrefix
std::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< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
LIBRARY_TABLE * GlobalTable() const
Retrieves the global library table for this adapter type.
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 std::mutex & globalLibsMutex()=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.
std::optional< wxString > FindLibraryByURI(const wxString &aURI) const
virtual std::optional< LIB_STATUS > GetLibraryStatus(const wxString &aNickname) const =0
Returns the status of a loaded library, or nullopt if the library hasn't been loaded (yet)
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.
std::vector< std::future< void > > m_futures
static wxString getUri(const LIBRARY_TABLE_ROW *aRow)
LIBRARY_RESULT< LIB_DATA * > loadFromScope(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope, std::map< wxString, LIB_DATA > &aTarget, std::mutex &aMutex)
bool CreateLibrary(const wxString &aNickname)
Creates the library (i.e. saves to disk) for the given row if it exists.
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_loadCount
LIBRARY_MANAGER & m_manager
std::optional< const LIB_DATA * > fetchIfLoaded(const wxString &aNickname) const
static wxString ExpandURI(const wxString &aShortURI, const PROJECT &aProject)
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)
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)
static std::vector< LIBRARY_TABLE_TYPE > InvalidGlobalTables()
void createEmptyTable(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
static bool GlobalTablesValid()
std::map< LIBRARY_TABLE_TYPE, std::unique_ptr< LIBRARY_TABLE > > m_projectTables
void LoadProjectTables(const wxString &aProjectPath)
std::optional< LIBRARY_TABLE_ROW * > FindRowByURI(LIBRARY_TABLE_TYPE aType, const wxString &aUri, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
static bool CreateGlobalTable(LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries)
void loadTables(const wxString &aTablePath, LIBRARY_TABLE_SCOPE aScope, std::vector< LIBRARY_TABLE_TYPE > aTablesToLoad={})
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
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
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
std::optional< LIBRARY_TABLE_ROW * > GetRow(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
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)
static const wxString TABLE_TYPE_NAME
void SetURI(const wxString &aUri)
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:308
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:612
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:783
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:129
Container for project specific data.
Definition project.h:65
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:509
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:629
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
SETTINGS_MANAGER * GetSettingsManager()
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:946
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
wxString result
Test unit parsing edge cases and error handling.
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