KiCad PCB EDA Suite
Loading...
Searching...
No Matches
http_lib_connection.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 along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <wx/log.h>
22#include <fmt/core.h>
23#include <wx/translation.h>
24#include <ctime>
25
26#include <boost/algorithm/string.hpp>
27#include <json_common.h>
28#include <wx/base64.h>
29
31#include <curl/curl.h>
32
34
35const char* const traceHTTPLib = "KICAD_HTTP_LIB";
36
37
38HTTP_LIB_CONNECTION::HTTP_LIB_CONNECTION( const HTTP_LIB_SOURCE& aSource, bool aTestConnectionNow )
39{
40 m_source = aSource;
41
42 if( aTestConnectionNow )
44}
45
46
48{
49 m_endpointValid = false;
50 std::string res = "";
51
52 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
53 curl->SetURL( m_source.root_url );
54
55 try
56 {
57 curl->Perform();
58
59 res = curl->GetBuffer();
60
61 if( !checkServerResponse( curl ) )
62 return false;
63
64 if( res.length() == 0 )
65 {
66 m_lastError += wxString::Format( _( "KiCad received an empty response!" ) + "\n" );
67 }
68 else
69 {
70 nlohmann::json response = nlohmann::json::parse( res );
71
72 // Check that the endpoints exist, if not fail.
73 if( !response.at( "categories" ).empty() && !response.at( "parts" ).empty() )
74 m_endpointValid = true;
75 }
76 }
77 catch( const std::exception& e )
78 {
79 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
80 e.what(), res );
81
82 wxLogTrace( traceHTTPLib, wxT( "validateHttpLibraryEndpoints: Exception while testing API connection: %s" ),
84
85 m_endpointValid = false;
86 }
87
88 if( m_endpointValid )
90
91 return m_endpointValid;
92}
93
94
96{
97 if( !IsValidEndpoint() )
98 {
99 wxLogTrace( traceHTTPLib, wxT( "syncCategories: without valid connection!" ) );
100 return false;
101 }
102
103 std::string res = "";
104
105 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
106 curl->SetURL( m_source.root_url + "categories.json" );
107
108 try
109 {
110 curl->Perform();
111
112 res = curl->GetBuffer();
113
114 if( !checkServerResponse( curl ) )
115 return false;
116
117 nlohmann::json response = nlohmann::json::parse( res );
118
119 // collect the categories in vector
120 for( const auto& item : response.items() )
121 {
122 HTTP_LIB_CATEGORY category;
123
124 auto& value = item.value();
125 category.id = value["id"].get<std::string>();
126 category.name = value["name"].get<std::string>();
127
128 if( value.contains( "description" ) )
129 {
130 category.description = value["description"].get<std::string>();
131 m_categoryDescriptions[category.name] = category.description;
132 }
133
134 m_categories.push_back( category );
135 }
136 }
137 catch( const std::exception& e )
138 {
139 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
140 e.what(), res );
141
142 wxLogTrace( traceHTTPLib, wxT( "syncCategories: Exception while syncing categories: %s" ), m_lastError );
143
144 m_categories.clear();
145
146 return false;
147 }
148
149 return true;
150}
151
152
153bool HTTP_LIB_CONNECTION::SelectOne( const std::string& aPartID, HTTP_LIB_PART& aFetchedPart )
154{
155 if( !IsValidEndpoint() )
156 {
157 wxLogTrace( traceHTTPLib, wxT( "SelectOne: without valid connection!" ) );
158 return false;
159 }
160
161 // Check if there is already a part in our cache, if not fetch it
162 if( m_cachedParts.find( aPartID ) != m_cachedParts.end() )
163 {
164 // check if it's outdated, if so re-fetch
165 if( std::difftime( std::time( nullptr ), m_cachedParts[aPartID].lastCached ) < m_source.timeout_parts )
166 {
167 aFetchedPart = m_cachedParts[aPartID];
168 return true;
169 }
170 }
171
172 std::string res = "";
173
174 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
175 std::string url = m_source.root_url + fmt::format( "parts/{}.json", aPartID );
176 curl->SetURL( url );
177
178 try
179 {
180 curl->Perform();
181
182 res = curl->GetBuffer();
183
184 if( !checkServerResponse( curl ) )
185 return false;
186
187 nlohmann::ordered_json response = nlohmann::ordered_json::parse( res );
188 std::string key = "";
189 std::string value = "";
190
191 // the id used to identify the part, the name is needed to show a human-readable
192 // part description to the user inside the symbol chooser dialog
193 aFetchedPart.id = response.at( "id" );
194
195 // get a timestamp for caching
196 aFetchedPart.lastCached = std::time( nullptr );
197
198 // API might not want to return an optional name.
199 if( response.contains( "name" ) )
200 aFetchedPart.name = response.at( "name" );
201 else
202 aFetchedPart.name = aFetchedPart.id;
203
204 aFetchedPart.symbolIdStr = response.at( "symbolIdStr" );
205
206 // initially assume no exclusion
207 std::string exclude;
208
209 if( response.contains( "exclude_from_bom" ) )
210 {
211 // if key value doesn't exists default to false
212 exclude = response.at( "exclude_from_bom" );
213 aFetchedPart.exclude_from_bom = boolFromString( exclude, false );
214 }
215
216 // initially assume no exclusion
217 if( response.contains( "exclude_from_board" ) )
218 {
219 // if key value doesn't exists default to false
220 exclude = response.at( "exclude_from_board" );
221 aFetchedPart.exclude_from_board = boolFromString( exclude, false );
222 }
223
224 // initially assume no exclusion
225 if( response.contains( "exclude_from_sim" ) )
226 {
227 // if key value doesn't exists default to false
228 exclude = response.at( "exclude_from_sim" );
229 aFetchedPart.exclude_from_sim = boolFromString( exclude, false );
230 }
231
232 // remove previously loaded fields
233 aFetchedPart.fields.clear();
234
235 // Extract available fields
236 for( const auto& field : response.at( "fields" ).items() )
237 {
238 bool visible = true;
239
240 // name of the field
241 key = field.key();
242
243 // this is a dict
244 auto& properties = field.value();
245
246 value = properties.at( "value" );
247
248 // check if user wants to display field in schematic
249 if( properties.contains( "visible" ) )
250 {
251 std::string vis = properties.at( "visible" );
252 visible = boolFromString( vis, true );
253 }
254
255 // Add field to fields list
256 aFetchedPart.fields.push_back( std::make_pair( key, std::make_tuple( value, visible ) ) );
257 }
258
259 if( response.contains( "description" ) )
260 aFetchedPart.desc = response.at( "description" );
261
262 if( response.contains( "keywords" ) )
263 aFetchedPart.keywords = response.at( "keywords" );
264
265 if( response.contains( "footprint_filters" ) )
266 {
267 nlohmann::json filters_json = response.at( "footprint_filters" );
268
269 if( filters_json.is_array() )
270 {
271 for( const auto& val : filters_json )
272 aFetchedPart.fp_filters.push_back( val );
273 }
274 else
275 {
276 aFetchedPart.fp_filters.push_back( filters_json );
277 }
278 }
279 }
280 catch( const std::exception& e )
281 {
282 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
283 e.what(), res );
284
285 wxLogTrace( traceHTTPLib, wxT( "SelectOne: Exception while fetching part: %s" ), m_lastError );
286
287 return false;
288 }
289
290 m_cachedParts[aFetchedPart.id] = aFetchedPart;
291
292 return true;
293}
294
295
296bool HTTP_LIB_CONNECTION::SelectAll( const HTTP_LIB_CATEGORY& aCategory, std::vector<HTTP_LIB_PART>& aParts )
297{
298 if( !IsValidEndpoint() )
299 {
300 wxLogTrace( traceHTTPLib, wxT( "SelectAll: without valid connection!" ) );
301 return false;
302 }
303
304 std::string res = "";
305
306 std::unique_ptr<KICAD_CURL_EASY> curl = createCurlEasyObject();
307
308 curl->SetURL( m_source.root_url + fmt::format( "parts/category/{}.json", aCategory.id ) );
309
310 try
311 {
312 curl->Perform();
313
314 res = curl->GetBuffer();
315
316 nlohmann::json response = nlohmann::json::parse( res );
317
318 for( nlohmann::json& item : response )
319 {
320 //PART result;
321 HTTP_LIB_PART part;
322
323 part.id = item.at( "id" );
324
325 if( item.contains( "description" ) )
326 {
327 // At this point we don't display anything so just set it to false
328 part.fields.push_back( std::make_pair( "description",
329 std::make_tuple( item.at( "description" ), false ) ) );
330 }
331
332 // API might not want to return an optional name.
333 if( item.contains( "name" ) )
334 part.name = item.at( "name" );
335 else
336 part.name = part.id;
337
338 // add to cache
339 m_cache[part.name] = std::make_tuple( part.id, aCategory.id );
340
341 aParts.emplace_back( std::move( part ) );
342 }
343 }
344 catch( const std::exception& e )
345 {
346 m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
347 e.what(), res );
348
349 wxLogTrace( traceHTTPLib, wxT( "Exception occurred while syncing parts: %s" ), m_lastError );
350
351 return false;
352 }
353
354 return true;
355}
356
357
358bool HTTP_LIB_CONNECTION::checkServerResponse( std::unique_ptr<KICAD_CURL_EASY>& aCurl )
359{
360 int statusCode = aCurl->GetResponseStatusCode();
361
362 if( statusCode != 200 )
363 {
364 m_lastError += wxString::Format( _( "API responded with error code: %s" ) + "\n",
365 httpErrorCodeDescription( statusCode ) );
366 return false;
367 }
368
369 return true;
370}
371
372
373bool HTTP_LIB_CONNECTION::boolFromString( const std::any& aVal, bool aDefaultValue )
374{
375 try
376 {
377 wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
378
379 if( strval.IsEmpty() )
380 return aDefaultValue;
381
382 strval.MakeLower();
383
384 for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
385 {
386 if( strval.Matches( trueVal ) )
387 return true;
388 }
389
390 for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
391 {
392 if( strval.Matches( falseVal ) )
393 return false;
394 }
395 }
396 catch( const std::bad_any_cast& )
397 {
398 }
399
400 return aDefaultValue;
401}
402
403
405{
406 auto codeDescription =
407 []( uint16_t aCode ) -> wxString
408 {
409 switch( aCode )
410 {
411 case 100: return wxS( "Continue" );
412 case 101: return wxS( "Switching Protocols" );
413 case 102: return wxS( "Processing" );
414 case 103: return wxS( "Early Hints" );
415
416 case 200: return wxS( "OK" );
417 case 201: return wxS( "Created" );
418 case 203: return wxS( "Non-Authoritative Information" );
419 case 204: return wxS( "No Content" );
420 case 205: return wxS( "Reset Content" );
421 case 206: return wxS( "Partial Content" );
422 case 207: return wxS( "Multi-Status" );
423 case 208: return wxS( "Already Reported" );
424 case 226: return wxS( "IM Used" );
425
426 case 300: return wxS( "Multiple Choices" );
427 case 301: return wxS( "Moved Permanently" );
428 case 302: return wxS( "Found" );
429 case 303: return wxS( "See Other" );
430 case 304: return wxS( "Not Modified" );
431 case 305: return wxS( "Use Proxy (Deprecated)" );
432 case 306: return wxS( "Unused" );
433 case 307: return wxS( "Temporary Redirect" );
434 case 308: return wxS( "Permanent Redirect" );
435
436 case 400: return wxS( "Bad Request" );
437 case 401: return wxS( "Unauthorized" );
438 case 402: return wxS( "Payment Required (Experimental)" );
439 case 403: return wxS( "Forbidden" );
440 case 404: return wxS( "Not Found" );
441 case 405: return wxS( "Method Not Allowed" );
442 case 406: return wxS( "Not Acceptable" );
443 case 407: return wxS( "Proxy Authentication Required" );
444 case 408: return wxS( "Request Timeout" );
445 case 409: return wxS( "Conflict" );
446 case 410: return wxS( "Gone" );
447 case 411: return wxS( "Length Required" );
448 case 412: return wxS( "Payload Too Large" );
449 case 414: return wxS( "URI Too Long" );
450 case 415: return wxS( "Unsupported Media Type" );
451 case 416: return wxS( "Range Not Satisfiable" );
452 case 417: return wxS( "Expectation Failed" );
453 case 418: return wxS( "I'm a teapot" );
454 case 421: return wxS( "Misdirected Request" );
455 case 422: return wxS( "Unprocessable Content" );
456 case 423: return wxS( "Locked" );
457 case 424: return wxS( "Failed Dependency" );
458 case 425: return wxS( "Too Early (Experimental)" );
459 case 426: return wxS( "Upgrade Required" );
460 case 428: return wxS( "Precondition Required" );
461 case 429: return wxS( "Too Many Requests" );
462 case 431: return wxS( "Request Header Fields Too Large" );
463 case 451: return wxS( "Unavailable For Legal Reasons" );
464
465 case 500: return wxS( "Internal Server Error" );
466 case 501: return wxS( "Not Implemented" );
467 case 502: return wxS( "Bad Gateway" );
468 case 503: return wxS( "Service Unavailable" );
469 case 504: return wxS( "Gateway Timeout" );
470 case 505: return wxS( "HTTP Version Not Supported" );
471 case 506: return wxS( "Variant Also Negotiates" );
472 case 507: return wxS( "Insufficient Storage" );
473 case 508: return wxS( "Loop Detected" );
474 case 510: return wxS( "Not Extended" );
475 case 511: return wxS( "Network Authentication Required" );
476 default: return wxS( "Unknown" );
477 }
478 };
479
480 return wxString::Format( wxS( "%d: %s" ), aHttpCode, codeDescription( aHttpCode ) );
481}
bool checkServerResponse(std::unique_ptr< KICAD_CURL_EASY > &aCurl)
std::map< std::string, HTTP_LIB_PART > m_cachedParts
std::unique_ptr< KICAD_CURL_EASY > createCurlEasyObject()
std::map< std::string, std::string > m_categoryDescriptions
bool boolFromString(const std::any &aVal, bool aDefaultValue=false)
bool SelectAll(const HTTP_LIB_CATEGORY &aCategory, std::vector< HTTP_LIB_PART > &aParts)
Retrieve all parts from a specific category from the HTTP library.
HTTP_LIB_CONNECTION(const HTTP_LIB_SOURCE &aSource, bool aTestConnectionNow)
std::map< std::string, std::tuple< std::string, std::string > > m_cache
std::vector< HTTP_LIB_CATEGORY > m_categories
bool SelectOne(const std::string &aPartID, HTTP_LIB_PART &aFetchedPart)
Retrieve a single part with full details from the HTTP library.
wxString httpErrorCodeDescription(uint16_t aHttpCode)
HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
#define _(s)
const char *const traceHTTPLib
const char *const traceHTTPLib
std::string id
id of category
std::string name
name of category
std::string description
description of category
std::string symbolIdStr
std::string keywords
std::vector< std::string > fp_filters
std::time_t lastCached
std::vector< std::pair< std::string, std::tuple< std::string, bool > > > fields
VECTOR3I res