KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_io_http_lib.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 (C) 2023 Andre F. K. Iwers <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21
22#include <wx/log.h>
23#include <wx/tokenzr.h>
24
25#include <fmt.h>
26#include <lib_symbol.h>
27
30#include "sch_io_http_lib.h"
31#include <ki_exception.h>
32
33
35 SCH_IO( wxS( "HTTP library" ) ),
36 m_adapter( nullptr )
37{
38}
39
40
41void SCH_IO_HTTP_LIB::EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath,
42 const std::map<std::string, UTF8>* aProperties )
43{
44 std::vector<LIB_SYMBOL*> symbols;
45 EnumerateSymbolLib( symbols, aLibraryPath, aProperties );
46
47 for( LIB_SYMBOL* symbol : symbols )
48 aSymbolNameList.Add( symbol->GetName() );
49}
50
51
52void SCH_IO_HTTP_LIB::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList, const wxString& aLibraryPath,
53 const std::map<std::string, UTF8>* aProperties )
54{
55 wxCHECK_RET( m_adapter, "HTTP plugin missing library manager adapter handle!" );
56 ensureSettings( aLibraryPath );
58
59 if( !m_conn )
61
62 bool powerSymbolsOnly = ( aProperties && aProperties->contains( SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly ) );
63
64 for( const HTTP_LIB_CATEGORY& category : m_conn->getCategories() )
65 {
66 bool refresh_cache = true;
67
68 // Check if there is already a part in our cache, if not fetch it
69 if( m_cachedCategories.find( category.id ) != m_cachedCategories.end() )
70 {
71 // check if it's outdated, if so re-fetch
72 if( std::difftime( std::time( nullptr ), m_cachedCategories[category.id].lastCached )
73 < m_settings->m_Source.timeout_categories )
74 {
75 refresh_cache = false;
76 }
77 }
78
79 if( refresh_cache )
80 {
81 syncCache( category );
82 }
83
84 for( const HTTP_LIB_PART& part : m_cachedCategories[category.id].cachedParts )
85 {
86 wxString libIDString( part.name );
87
88 LIB_SYMBOL* symbol = loadSymbolFromPart( aLibraryPath, libIDString, category, part );
89
90 if( symbol && ( !powerSymbolsOnly || symbol->IsPower() ) )
91 aSymbolList.emplace_back( symbol );
92 }
93 }
94}
95
96
97LIB_SYMBOL* SCH_IO_HTTP_LIB::LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName,
98 const std::map<std::string, UTF8>* aProperties )
99{
100 wxCHECK_MSG( m_adapter, nullptr, "HTTP plugin missing library manager adapter handle!" );
101 ensureSettings( aLibraryPath );
103
104 if( !m_conn )
106
107 std::string part_id = "";
108
109 std::string partName( aAliasName.ToUTF8() );
110
111 const HTTP_LIB_CATEGORY* foundCategory = nullptr;
113
114 std::vector<HTTP_LIB_CATEGORY> categories = m_conn->getCategories();
115
116 if( m_conn->GetCachedParts().empty() )
117 syncCache();
118
119 std::tuple relations = m_conn->GetCachedParts()[partName];
120 std::string associatedCatID = std::get<1>( relations );
121
122 // get the matching category
123 for( const HTTP_LIB_CATEGORY& categoryIter : categories )
124 {
125 if( categoryIter.id == associatedCatID )
126 {
127 foundCategory = &categoryIter;
128 break;
129 }
130 }
131
132 // return Null if no category was found. This should never happen
133 if( foundCategory == nullptr )
134 {
135 wxLogTrace( traceHTTPLib, wxT( "loadSymbol: no category found for %s" ), partName );
136 return nullptr;
137 }
138
139 // get the matching query ID
140 for( const HTTP_LIB_PART& part : m_cachedCategories[foundCategory->id].cachedParts )
141 {
142 if( part.id == std::get<0>( relations ) )
143 {
144 part_id = part.id;
145 break;
146 }
147 }
148
149 if( m_conn->SelectOne( part_id, result ) )
150 {
151 wxLogTrace( traceHTTPLib, wxT( "LoadSymbol: SelectOne (%s) found in %s" ), part_id, foundCategory->name );
152 }
153 else
154 {
155 wxLogTrace( traceHTTPLib, wxT( "LoadSymbol: SelectOne (%s) failed for category %s" ), part_id,
156 foundCategory->name );
157
159 }
160
161 wxCHECK( foundCategory, nullptr );
162
163 return loadSymbolFromPart( aLibraryPath, aAliasName, *foundCategory, result );
164}
165
166
167void SCH_IO_HTTP_LIB::GetSubLibraryNames( std::vector<wxString>& aNames )
168{
169 aNames.clear();
170
171 ensureSettings( wxEmptyString );
172 connect();
173
174 // connect() leaves m_conn null when the endpoint is unreachable so a network loss
175 // degrades to an empty result instead of a null dereference while building the tree.
176 if( !m_conn )
177 return;
178
179 std::set<wxString> categoryNames;
180
181 for( const HTTP_LIB_CATEGORY& categoryIter : m_conn->getCategories() )
182 {
183 if( categoryNames.count( categoryIter.name ) )
184 continue;
185
186 aNames.emplace_back( categoryIter.name );
187 categoryNames.insert( categoryIter.name );
188 }
189}
190
191
192wxString SCH_IO_HTTP_LIB::GetSubLibraryDescription( const wxString& aName )
193{
194 ensureSettings( wxEmptyString );
195 connect();
196
197 if( !m_conn )
198 return wxEmptyString;
199
200 return m_conn->getCategoryDescription( std::string( aName.mb_str() ) );
201}
202
203
204void SCH_IO_HTTP_LIB::GetAvailableSymbolFields( std::vector<wxString>& aNames )
205{
206 // TODO: Implement this sometime; This is currently broken...
207 std::copy( m_customFields.begin(), m_customFields.end(), std::back_inserter( aNames ) );
208}
209
210
211void SCH_IO_HTTP_LIB::GetDefaultSymbolFields( std::vector<wxString>& aNames )
212{
213 std::copy( m_defaultShownFields.begin(), m_defaultShownFields.end(), std::back_inserter( aNames ) );
214}
215
216
217void SCH_IO_HTTP_LIB::ensureSettings( const wxString& aSettingsPath )
218{
219 auto tryLoad =
220 [&]()
221 {
222 if( !m_settings->LoadFromFile() )
223 {
224 THROW_IO_ERROR( wxString::Format( _( "HTTP library settings file %s missing or invalid." ),
225 aSettingsPath ) );
226 }
227
228 if( m_settings->m_Source.api_version.empty() )
229 {
230 THROW_IO_ERROR( wxString::Format( _( "HTTP library settings file %s is missing the API version "
231 "number." ),
232 aSettingsPath ) );
233 }
234
235 if( m_settings->getSupportedAPIVersion() != m_settings->m_Source.api_version )
236 {
237 THROW_IO_ERROR( wxString::Format( _( "HTTP library settings file %s uses API version %s, but "
238 "KiCad requires version %s." ),
239 aSettingsPath, m_settings->m_Source.api_version,
240 m_settings->getSupportedAPIVersion() ) );
241 }
242
243 if( m_settings->m_Source.root_url.empty() )
244 {
245 THROW_IO_ERROR( wxString::Format( _( "HTTP library settings file %s is missing the root URL." ),
246 aSettingsPath ) );
247 }
248
249 // map lib source type
250 m_settings->m_Source.type = m_settings->get_HTTP_LIB_SOURCE_TYPE();
251
252 if( m_settings->m_Source.type == HTTP_LIB_SOURCE_TYPE::INVALID )
253 {
254 THROW_IO_ERROR( wxString::Format( _( "HTTP library settings file %s has invalid library type." ),
255 aSettingsPath ) );
256 }
257
258 // make sure that the root url finishes with a forward slash
259 if( m_settings->m_Source.root_url.at( m_settings->m_Source.root_url.length() - 1 ) != '/' )
260 m_settings->m_Source.root_url += "/";
261
262 // Append api version to root URL
263 m_settings->m_Source.root_url += m_settings->m_Source.api_version + "/";
264 };
265
266 if( !m_settings && !aSettingsPath.IsEmpty() )
267 {
268 std::string path( aSettingsPath.ToUTF8() );
269 m_settings = std::make_unique<HTTP_LIB_SETTINGS>( path );
270
271 m_settings->SetReadOnly( true );
272
273 tryLoad();
274 }
275 else if( m_settings )
276 {
277 // If we have valid settings but no connection yet; reload settings in case user is editing
278 tryLoad();
279 }
280 else if( !m_settings )
281 {
282 wxLogTrace( traceHTTPLib, wxT( "ensureSettings: no settings available!" ) );
283 }
284}
285
286
288{
289 wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" );
290
291 connect();
292
293 if( !m_conn || !m_conn->IsValidEndpoint() )
294 {
295 THROW_IO_ERROR( wxString::Format( _( "Could not connect to %s. Errors: %s" ),
296 m_settings->m_Source.root_url,
297 m_lastError ) );
298 }
299}
300
301
303{
304 wxCHECK_RET( m_settings, "Call ensureSettings before connect()!" );
305
306 if( !m_conn )
307 {
308 m_conn = std::make_unique<HTTP_LIB_CONNECTION>( m_settings->m_Source, true );
309
310 if( !m_conn->IsValidEndpoint() )
311 {
312 m_lastError = m_conn->GetLastError();
313
314 // Make sure we release pointer so we are able to query API again next time
315 m_conn.reset();
316
317 return;
318 }
319 }
320}
321
322
324{
325 for( const HTTP_LIB_CATEGORY& category : m_conn->getCategories() )
326 syncCache( category );
327}
328
329
331{
332 std::vector<HTTP_LIB_PART> found_parts;
333
334 if( !m_conn->SelectAll( category, found_parts ) )
335 {
336 if( !m_conn->GetLastError().empty() )
337 {
338 THROW_IO_ERROR( wxString::Format( _( "Error retrieving data from HTTP library %s: %s" ),
339 category.name,
340 m_conn->GetLastError() ) );
341 }
342
343 return;
344 }
345
346 // remove cached parts
347 m_cachedCategories[category.id].cachedParts.clear();
348
349 // Copy newly cached data across
350 m_cachedCategories[category.id].cachedParts = found_parts;
351 m_cachedCategories[category.id].lastCached = std::time( nullptr );
352}
353
354
355LIB_SYMBOL* SCH_IO_HTTP_LIB::loadSymbolFromPart( const wxString& aLibraryPath,
356 const wxString& aSymbolName,
357 const HTTP_LIB_CATEGORY& aCategory,
358 const HTTP_LIB_PART& aPart )
359{
360 LIB_SYMBOL* symbol = nullptr;
361 LIB_SYMBOL* originalSymbol = nullptr;
362 LIB_ID symbolId;
363
364 std::string symbolIdStr = aPart.symbolIdStr;
365
366 // Extract library nickname from the library path (e.g., "/path/to/W5.kicad_httplib" -> "W5")
367 wxFileName libFileName( aLibraryPath );
368 wxString libNickname = libFileName.GetName();
369
370 // Get or Create the symbol using the found symbol
371 if( !symbolIdStr.empty() )
372 {
373 symbolId.Parse( symbolIdStr );
374
375 if( symbolId.IsValid() )
376 originalSymbol = m_adapter->LoadSymbol( symbolId );
377
378 if( originalSymbol )
379 {
380 wxLogTrace( traceHTTPLib, wxT( "loadSymbolFromPart: found original symbol '%s'" ), symbolIdStr );
381
382 symbol = originalSymbol->Duplicate();
383 symbol->SetSourceLibId( symbolId );
384 symbol->SetName( aSymbolName );
385
386 LIB_ID libId = symbol->GetLibId();
387 libId.SetLibNickname( libNickname );
388 libId.SetSubLibraryName( aCategory.name );
389 symbol->SetLibId( libId );
390 }
391 else if( !symbolId.IsValid() )
392 {
393 wxLogTrace( traceHTTPLib, wxT( "loadSymbolFromPart: source symbol id '%s' is invalid, "
394 "will create empty symbol" ), symbolIdStr );
395 }
396 else
397 {
398 wxLogTrace( traceHTTPLib, wxT( "loadSymbolFromPart: source symbol '%s' not found, "
399 "will create empty symbol" ), symbolIdStr );
400 }
401 }
402
403 if( !symbol )
404 {
405 // Actual symbol not found: return metadata only; error will be
406 // indicated in the symbol chooser
407 symbol = new LIB_SYMBOL( aSymbolName );
408
409 LIB_ID libId = symbol->GetLibId();
410 libId.SetLibNickname( libNickname );
411 libId.SetSubLibraryName( aCategory.name );
412 symbol->SetLibId( libId );
413 }
414
415 symbol->SetExcludedFromBOM( aPart.exclude_from_bom );
417 symbol->SetExcludedFromSim( aPart.exclude_from_sim );
418
419 wxArrayString fp_filters;
420
421 for( auto& [fieldName, fieldProperties] : aPart.fields )
422 {
423 wxString lowerFieldName = wxString( fieldName ).Lower();
424
425 if( lowerFieldName == footprint_field )
426 {
427 SCH_FIELD* field = &symbol->GetFootprintField();
428 wxStringTokenizer tokenizer( std::get<0>( fieldProperties ), ";\t\r\n", wxTOKEN_STRTOK );
429
430 while( tokenizer.HasMoreTokens() )
431 fp_filters.Add( tokenizer.GetNextToken() );
432
433 if( fp_filters.size() > 0 )
434 field->SetText( fp_filters[0] );
435
436 field->SetVisible( std::get<1>( fieldProperties ) );
437 }
438 else if( lowerFieldName == description_field )
439 {
440 SCH_FIELD* field = &symbol->GetDescriptionField();
441 field->SetText( std::get<0>( fieldProperties ) );
442 field->SetVisible( std::get<1>( fieldProperties ) );
443 }
444 else if( lowerFieldName == value_field )
445 {
446 SCH_FIELD* field = &symbol->GetValueField();
447 field->SetText( std::get<0>( fieldProperties ) );
448 field->SetVisible( std::get<1>( fieldProperties ) );
449 }
450 else if( lowerFieldName == datasheet_field )
451 {
452 SCH_FIELD* field = &symbol->GetDatasheetField();
453 field->SetText( std::get<0>( fieldProperties ) );
454 field->SetVisible( std::get<1>( fieldProperties ) );
455 }
456 else if( lowerFieldName == reference_field )
457 {
458 SCH_FIELD* field = &symbol->GetReferenceField();
459 field->SetText( std::get<0>( fieldProperties ) );
460 field->SetVisible( std::get<1>( fieldProperties ) );
461 }
462 else if( lowerFieldName == keywords_field )
463 {
464 symbol->SetKeyWords( std::get<0>( fieldProperties ) );
465 }
466 else
467 {
468 // Check if field exists, if so replace Text and adjust visiblity.
469 //
470 // This proves useful in situations where, for instance, an individual requires a particular value, such as
471 // the material type showcased at a specific position for a capacitor. Subsequently, this value could be defined
472 // in the symbol itself and then, potentially, be modified by the HTTP library as necessary.
473 SCH_FIELD* field = symbol->GetField( fieldName );
474
475 if( field != nullptr )
476 {
477 // adjust values accordingly
478 field->SetText( std::get<0>( fieldProperties ) );
479 field->SetVisible( std::get<1>( fieldProperties ) );
480 }
481 else
482 {
483 // Generic fields
484 field = new SCH_FIELD( symbol, FIELD_T::USER );
485 field->SetName( fieldName );
486
487 field->SetText( std::get<0>( fieldProperties ) );
488 field->SetVisible( std::get<1>( fieldProperties ) );
489 symbol->AddField( field );
490
491 m_customFields.insert( fieldName );
492 }
493 }
494 }
495
496 symbol->SetDescription( aPart.desc );
497 symbol->SetKeyWords( aPart.keywords );
498
499 for( const std::string& filter : aPart.fp_filters )
500 fp_filters.push_back( filter );
501
502 symbol->SetFPFilters( fp_filters );
503
504 return symbol;
505}
506
507void SCH_IO_HTTP_LIB::SaveSymbol( const wxString& aLibraryPath, const LIB_SYMBOL* aSymbol,
508 const std::map<std::string, UTF8>* aProperties )
509{
510 // TODO: Implement this sometime;
511}
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:48
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:96
void SetSubLibraryName(const UTF8 &aName)
Definition lib_id.h:127
Define a library symbol object.
Definition lib_symbol.h:79
SCH_FIELD & GetDescriptionField()
Return reference to the description field.
Definition lib_symbol.h:345
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:148
bool IsPower() const override
void SetSourceLibId(const LIB_ID &aLibId)
Definition lib_symbol.h:152
SCH_FIELD & GetDatasheetField()
Return reference to the datasheet field.
Definition lib_symbol.h:341
virtual LIB_SYMBOL * Duplicate() const
Create a copy of a LIB_SYMBOL and assigns unique KIIDs to the copy and its children.
Definition lib_symbol.h:93
SCH_FIELD * GetField(const wxString &aFieldName)
Find a field within this symbol matching aFieldName; return nullptr if not found.
SCH_FIELD & GetFootprintField()
Return reference to the footprint field.
Definition lib_symbol.h:337
void SetDescription(const wxString &aDescription)
Gets the Description field text value *‍/.
void SetKeyWords(const wxString &aKeyWords)
SCH_FIELD & GetValueField()
Return reference to the value field.
Definition lib_symbol.h:329
void SetFPFilters(const wxArrayString &aFilters)
Definition lib_symbol.h:205
void SetLibId(const LIB_ID &aLibId)
void AddField(SCH_FIELD *aField)
Add a field.
virtual void SetName(const wxString &aName)
SCH_FIELD & GetReferenceField()
Return reference to the reference designator field.
Definition lib_symbol.h:333
void SetName(const wxString &aName)
void SetText(const wxString &aText) override
std::unique_ptr< HTTP_LIB_CONNECTION > m_conn
Generally will be null if no valid connection is established.
LIB_SYMBOL * LoadSymbol(const wxString &aLibraryPath, const wxString &aAliasName, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a LIB_SYMBOL object having aPartName from the aLibraryPath containing a library format that this...
void SaveSymbol(const wxString &aLibraryPath, const LIB_SYMBOL *aSymbol, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aSymbol to an existing library located at aLibraryPath.
std::unique_ptr< HTTP_LIB_SETTINGS > m_settings
wxString GetSubLibraryDescription(const wxString &aName) override
Gets a description of a sublibrary.
SYMBOL_LIBRARY_ADAPTER * m_adapter
void ensureSettings(const wxString &aSettingsPath)
std::set< wxString > m_customFields
void GetDefaultSymbolFields(std::vector< wxString > &aNames) override
Retrieves a list of (custom) field names that should be shown by default for this library in the symb...
void GetAvailableSymbolFields(std::vector< wxString > &aNames) override
Retrieves a list of (custom) field names that are present on symbols in this library.
std::set< wxString > m_defaultShownFields
LIB_SYMBOL * loadSymbolFromPart(const wxString &aLibraryPath, const wxString &aSymbolName, const HTTP_LIB_CATEGORY &aCategory, const HTTP_LIB_PART &aPart)
wxString description_field
std::map< std::string, HTTP_LIB_CATEGORY > m_cachedCategories
void EnumerateSymbolLib(wxArrayString &aSymbolNameList, const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Populate a list of LIB_SYMBOL alias names contained within the library aLibraryPath.
void GetSubLibraryNames(std::vector< wxString > &aNames) override
Retrieves a list of sub-libraries in this library.
SCH_IO(const wxString &aName)
Definition sch_io.h:375
static const char * PropPowerSymsOnly
void SetExcludedFromBoard(bool aExclude, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear exclude from board netlist flag.
Definition symbol.h:206
virtual void SetExcludedFromSim(bool aExcludeFromSim, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear the exclude from simulation flag.
Definition symbol.h:176
virtual void SetExcludedFromBOM(bool aExcludeFromBOM, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear the exclude from schematic bill of materials flag.
Definition symbol.h:191
#define _(s)
const char *const traceHTTPLib
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
std::string id
id of category
std::string name
name of category
std::string symbolIdStr
std::string keywords
std::vector< std::string > fp_filters
std::vector< std::pair< std::string, std::tuple< std::string, bool > > > fields
@ USER
The field ID hasn't been set yet; field is invalid.
std::string path
wxString result
Test unit parsing edge cases and error handling.