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