ADDED: HTTP library support

Fixes https://gitlab.com/kicad/code/kicad/-/issues/12027
This commit is contained in:
Andre Iwers 2023-09-19 01:09:21 +00:00 committed by Jon Evans
parent b52c43b933
commit 2bcad18cc2
12 changed files with 1314 additions and 0 deletions

View File

@ -561,6 +561,10 @@ set( COMMON_SRCS
database/database_connection.cpp
database/database_lib_settings.cpp
http_lib/http_lib_connection.cpp
http_lib/http_lib_settings.cpp
)
add_library( common STATIC

View File

@ -0,0 +1,463 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/log.h>
#include <fmt/core.h>
#include <wx/translation.h>
#include <boost/algorithm/string.hpp>
#include <nlohmann/json.hpp>
#include <wx/base64.h>
#include <kicad_curl/kicad_curl_easy.h>
#include <curl/curl.h>
#include <http_lib/http_lib_connection.h>
const char* const traceHTTPLib = "KICAD_HTTP_LIB";
HTTP_LIB_CONNECTION::HTTP_LIB_CONNECTION( const HTTP_LIB_SOURCE& aSource, bool aTestConnectionNow )
{
m_rootURL = aSource.root_url;
m_token = aSource.token;
m_sourceType = aSource.type;
if( aTestConnectionNow )
{
ValidateHTTPLibraryEndpoints();
}
}
HTTP_LIB_CONNECTION::~HTTP_LIB_CONNECTION()
{
// Do nothing
}
bool HTTP_LIB_CONNECTION::ValidateHTTPLibraryEndpoints()
{
std::string res = "";
std::unique_ptr<KICAD_CURL_EASY> m_curl = createCurlEasyObject();
m_curl->SetURL( m_rootURL );
try
{
m_curl->Perform();
res = m_curl->GetBuffer();
if( !checkServerResponse( m_curl ) )
{
return false;
}
if( res.length() == 0 )
{
m_lastError += wxString::Format( _( "KiCad received an empty response!" ) + "\n" );
}
else
{
nlohmann::json response = nlohmann::json::parse( res );
// Check that the endpoints exist, if not fail.
if( !response.at( http_endpoint_categories ).empty()
&& !response.at( http_endpoint_parts ).empty() )
m_enpointValid = true;
else
m_enpointValid = false;
}
}
catch( const std::exception& e )
{
m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
e.what(), res );
wxLogTrace( traceHTTPLib,
wxT( "ValidateHTTPLibraryEndpoints: Exception occurred while testing the API "
"connection: %s" ),
m_lastError );
m_enpointValid = false;
}
if( IsValidEnpoint() )
{
syncCategories();
}
return IsValidEnpoint();
}
bool HTTP_LIB_CONNECTION::IsValidEnpoint() const
{
return m_enpointValid;
}
bool HTTP_LIB_CONNECTION::syncCategories()
{
if( !IsValidEnpoint() )
{
wxLogTrace( traceHTTPLib, wxT( "syncCategories: without valid connection!" ) );
return false;
}
std::string res = "";
std::unique_ptr<KICAD_CURL_EASY> m_curl = createCurlEasyObject();
m_curl->SetURL( m_rootURL + http_endpoint_categories + ".json" );
try
{
m_curl->Perform();
res = m_curl->GetBuffer();
if( !checkServerResponse( m_curl ) )
{
return false;
}
nlohmann::json response = nlohmann::json::parse( res );
// collect the categories in vector
for( const auto& item : response.items() )
{
HTTP_LIB_CATEGORY category;
category.id = item.value()["id"].get<std::string>();
category.name = item.value()["name"].get<std::string>();
m_categories.push_back( category );
}
}
catch( const std::exception& e )
{
m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
e.what(), res );
wxLogTrace( traceHTTPLib,
wxT( "syncCategories: Exception occurred while syncing categories: %s" ),
m_lastError );
m_categories.clear();
return false;
}
return true;
}
bool HTTP_LIB_CONNECTION::SelectOne( const std::string aPartID, HTTP_LIB_PART& aFetchedPart )
{
if( !IsValidEnpoint() )
{
wxLogTrace( traceHTTPLib, wxT( "SelectOne: without valid connection!" ) );
return false;
}
// if the same part is selected again, use cached part
// instead to minimise http requests.
if( m_cached_part.id == aPartID )
{
aFetchedPart = m_cached_part;
return true;
}
std::string res = "";
std::unique_ptr<KICAD_CURL_EASY> m_curl = createCurlEasyObject();
m_curl->SetURL( m_rootURL + fmt::format( http_endpoint_parts + "/{}.json", aPartID ) );
try
{
m_curl->Perform();
res = m_curl->GetBuffer();
if( !checkServerResponse( m_curl ) )
{
return false;
}
nlohmann::json response = nlohmann::json::parse( res );
std::string key = "";
std::string value = "";
// the id used to identify the part, the name is needed to show a human-readable
// part descirption to the user inside the symbol chooser dialog
aFetchedPart.id = response.at( "id" );
// API might not want to return an optional name.
if( response.contains( "name" ) )
{
aFetchedPart.name = response.at( "name" );
}
else
{
aFetchedPart.name = aFetchedPart.id;
}
aFetchedPart.symbolIdStr = response.at( "symbolIdStr" );
// Extract available fields
for( const auto& field : response.at( "fields" ).items() )
{
bool visible = true;
// name of the field
key = field.key();
// this is a dict
auto& properties = field.value();
value = properties.at( "value" );
// check if user wants to display field in schematic
if( properties.contains( "visible" ) )
{
std::string buf = properties.at( "visible" );
visible = boolFromString( buf );
}
// Add field to fields list
if( key.length() )
{
aFetchedPart.fields[key] = std::make_tuple( value, visible );
}
}
}
catch( const std::exception& e )
{
m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
e.what(), res );
wxLogTrace( traceHTTPLib,
wxT( "SelectOne: Exception occurred while retrieving part from REST API: %s" ),
m_lastError );
return false;
}
m_cached_part = aFetchedPart;
return true;
}
bool HTTP_LIB_CONNECTION::SelectAll( const HTTP_LIB_CATEGORY& aCategory,
std::vector<HTTP_LIB_PART>& aParts )
{
if( !IsValidEnpoint() )
{
wxLogTrace( traceHTTPLib, wxT( "SelectAll: without valid connection!" ) );
return false;
}
std::string res = "";
std::unique_ptr<KICAD_CURL_EASY> m_curl = createCurlEasyObject();
m_curl->SetURL( m_rootURL
+ fmt::format( http_endpoint_parts + "/category/{}.json", aCategory.id ) );
try
{
m_curl->Perform();
res = m_curl->GetBuffer();
nlohmann::json response = nlohmann::json::parse( res );
std::string key = "";
std::string value = "";
for( nlohmann::json item : response )
{
//PART result;
HTTP_LIB_PART part;
part.id = item.at( "id" );
// API might not want to return an optional name.
if( item.contains( "name" ) )
{
part.name = item.at( "name" );
}
else
{
part.name = part.id;
}
// add to cache
m_cache[part.name] = std::make_tuple( part.id, aCategory.id );
aParts.emplace_back( std::move( part ) );
}
}
catch( const std::exception& e )
{
m_lastError += wxString::Format( _( "Error: %s" ) + "\n" + _( "API Response: %s" ) + "\n",
e.what(), res );
wxLogTrace( traceHTTPLib, wxT( "Exception occurred while syncing parts from REST API: %s" ),
m_lastError );
return false;
}
return true;
}
bool HTTP_LIB_CONNECTION::checkServerResponse( std::unique_ptr<KICAD_CURL_EASY>& m_curl )
{
long http_code = 0;
curl_easy_getinfo( m_curl->GetCurl(), CURLINFO_RESPONSE_CODE, &http_code );
if( http_code != 200 )
{
m_lastError += wxString::Format( _( "API responded with error code: %s" ) + "\n",
httpErrorCodeDescription( http_code ) );
return false;
}
return true;
}
bool HTTP_LIB_CONNECTION::boolFromString( const std::any& aVal )
{
try
{
wxString strval( std::any_cast<std::string>( aVal ).c_str(), wxConvUTF8 );
if( strval.IsEmpty() )
return true;
strval.MakeLower();
for( const auto& trueVal : { wxS( "true" ), wxS( "yes" ), wxS( "y" ), wxS( "1" ) } )
{
if( strval.Matches( trueVal ) )
return true;
}
for( const auto& falseVal : { wxS( "false" ), wxS( "no" ), wxS( "n" ), wxS( "0" ) } )
{
if( strval.Matches( falseVal ) )
return false;
}
}
catch( const std::bad_any_cast& )
{
}
return true;
}
/*
* HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
* Responses are grouped in five classes:
* Informational responses (100 ? 199)
* Successful responses (200 ? 299)
* Redirection messages (300 ? 399)
* Client error responses (400 ? 499)
* Server error responses (500 ? 599)
*
* see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
*/
wxString HTTP_LIB_CONNECTION::httpErrorCodeDescription( uint16_t http_code )
{
switch( http_code )
{
case 100: return "100" + _( "Continue" );
case 101: return "101" + _( "Switching Protocols" );
case 102: return "102" + _( "Processing" );
case 103: return "103" + _( "Early Hints" );
case 200: return "200" + _( "OK" );
case 201: return "201" + _( "Created" );
case 203: return "203" + _( "Non-Authoritative Information" );
case 204: return "204" + _( "No Content" );
case 205: return "205" + _( "Reset Content" );
case 206: return "206" + _( "Partial Content" );
case 207: return "207" + _( "Multi-Status" );
case 208: return "208" + _( "Already Reporte" );
case 226: return "226" + _( "IM Used" );
case 300: return "300" + _( "Multiple Choices" );
case 301: return "301" + _( "Moved Permanently" );
case 302: return "302" + _( "Found" );
case 303: return "303" + _( "See Other" );
case 304: return "304" + _( "Not Modified" );
case 305: return "305" + _( "Use Proxy (Deprecated)" );
case 306: return "306" + _( "Unused" );
case 307: return "307" + _( "Temporary Redirect" );
case 308: return "308" + _( "Permanent Redirect" );
case 400: return "400" + _( "Bad Request" );
case 401: return "401" + _( "Unauthorized" );
case 402: return "402" + _( "Payment Required (Experimental)" );
case 403: return "403" + _( "Forbidden" );
case 404: return "404" + _( "Not Found" );
case 405: return "405" + _( "Method Not Allowed" );
case 406: return "406" + _( "Not Acceptable" );
case 407: return "407" + _( "Proxy Authentication Required" );
case 408: return "408" + _( "Request Timeout" );
case 409: return "409" + _( "Conflict" );
case 410: return "410" + _( "Gone" );
case 411: return "411" + _( "Length Required" );
case 412: return "413" + _( "Payload Too Large" );
case 414: return "414" + _( "URI Too Long" );
case 415: return "415" + _( "Unsupported Media Type" );
case 416: return "416" + _( "Range Not Satisfiable" );
case 417: return "417" + _( "Expectation Failed" );
case 418: return "418" + _( "I'm a teapot" );
case 421: return "421" + _( "Misdirected Request" );
case 422: return "422" + _( "Unprocessable Conten" );
case 423: return "423" + _( "Locked" );
case 424: return "424" + _( "Failed Dependency" );
case 425: return "425" + _( "Too Early (Experimental)" );
case 426: return "426" + _( "Upgrade Required" );
case 428: return "428" + _( "Precondition Required" );
case 429: return "429" + _( "Too Many Requests" );
case 431: return "431" + _( "Request Header Fields Too Large" );
case 451: return "451" + _( "Unavailable For Legal Reasons" );
case 500: return "500" + _( "Internal Server Error" );
case 501: return "501" + _( "Not Implemented" );
case 502: return "502" + _( "Bad Gateway" );
case 503: return "503" + _( "Service Unavailable" );
case 504: return "504" + _( "Gateway Timeout" );
case 505: return "505" + _( "HTTP Version Not Supported" );
case 506: return "506" + _( "Variant Also Negotiates" );
case 507: return "507" + _( "Insufficient Storag" );
case 508: return "508" + _( "Loop Detecte" );
case 510: return "510" + _( "Not Extended" );
case 511: return "511" + _( "Network Authentication Required" );
}
return wxString::Format( _( "Code Unkonwn: %d" ), http_code );
}

View File

@ -0,0 +1,51 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <nlohmann/json.hpp>
#include <settings/parameters.h>
#include <wildcards_and_files_ext.h>
#include <http_lib/http_lib_settings.h>
const int httplibSchemaVersion = 1;
HTTP_LIB_SETTINGS::HTTP_LIB_SETTINGS( const std::string& aFilename ) :
JSON_SETTINGS( aFilename, SETTINGS_LOC::NONE, httplibSchemaVersion )
{
m_params.emplace_back( new PARAM<std::string>( "source.type", &sourceType, "" ) );
m_params.emplace_back(
new PARAM<std::string>( "source.api_version", &m_Source.api_version, "" ) );
m_params.emplace_back( new PARAM<std::string>( "source.root_url", &m_Source.root_url, "" ) );
m_params.emplace_back( new PARAM<std::string>( "source.token", &m_Source.token, "" ) );
}
wxString HTTP_LIB_SETTINGS::getFileExt() const
{
return HTTPLibraryFileExtension;
}

View File

@ -152,6 +152,7 @@ const std::string HtmlFileExtension( "html" );
const std::string EquFileExtension( "equ" );
const std::string HotkeyFileExtension( "hotkeys" );
const std::string DatabaseLibraryFileExtension( "kicad_dbl" );
const std::string HTTPLibraryFileExtension( "kicad_httplib" );
const std::string ArchiveFileExtension( "zip" );

View File

@ -395,6 +395,7 @@ set( EESCHEMA_SRCS
sch_plugins/legacy/sch_legacy_plugin.cpp
sch_plugins/legacy/sch_legacy_plugin_helpers.cpp
sch_plugins/database/sch_database_plugin.cpp
sch_plugins/http_lib/sch_http_lib_plugin.cpp
tools/assign_footprints.cpp
tools/backannotate.cpp

View File

@ -34,6 +34,7 @@
#include <sch_plugins/easyedapro/sch_easyedapro_plugin.h>
#include <sch_plugins/database/sch_database_plugin.h>
#include <sch_plugins/ltspice/ltspice_sch_plugin.h>
#include <sch_plugins/http_lib/sch_http_lib_plugin.h>
#include <common.h> // for ExpandEnvVarSubstitutions
#include <wildcards_and_files_ext.h>
@ -72,6 +73,7 @@ SCH_PLUGIN* SCH_IO_MGR::FindPlugin( SCH_FILE_T aFileType )
case SCH_EASYEDA: return new SCH_EASYEDA_PLUGIN();
case SCH_EASYEDAPRO: return new SCH_EASYEDAPRO_PLUGIN();
case SCH_LTSPICE: return new SCH_LTSPICE_PLUGIN();
case SCH_HTTP: return new SCH_HTTP_LIB_PLUGIN();
default: return nullptr;
}
}
@ -104,6 +106,7 @@ const wxString SCH_IO_MGR::ShowType( SCH_FILE_T aType )
case SCH_EASYEDA: return wxString( wxT( "EasyEDA (JLCEDA) Std" ) );
case SCH_EASYEDAPRO: return wxString( wxT( "EasyEDA (JLCEDA) Pro" ) );
case SCH_LTSPICE: return wxString( wxT( "LTspice" ) );
case SCH_HTTP: return wxString( wxT( "HTTP" ) );
default: return wxString::Format( _( "Unknown SCH_FILE_T value: %d" ),
aType );
}
@ -134,6 +137,8 @@ SCH_IO_MGR::SCH_FILE_T SCH_IO_MGR::EnumFromStr( const wxString& aType )
return SCH_EASYEDAPRO;
else if( aType == wxT( "LTspice" ) )
return SCH_LTSPICE;
else if( aType == wxT( "HTTP" ) )
return SCH_HTTP;
// wxASSERT( blow up here )

View File

@ -67,6 +67,7 @@ public:
SCH_EASYEDA, ///< EasyEDA Std schematic file
SCH_EASYEDAPRO, ///< EasyEDA Pro archive
SCH_LTSPICE, ///< LtSpice Schematic format
SCH_HTTP, ///< KiCad HTTP library
// Add your schematic type here.
SCH_FILE_UNKNOWN

View File

@ -0,0 +1,450 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/log.h>
#include <fmt.h>
#include <lib_symbol.h>
#include <symbol_lib_table.h>
#include <http_lib/http_lib_connection.h>
#include "sch_http_lib_plugin.h"
SCH_HTTP_LIB_PLUGIN::SCH_HTTP_LIB_PLUGIN() :
m_libTable( nullptr )
{
}
SCH_HTTP_LIB_PLUGIN::~SCH_HTTP_LIB_PLUGIN()
{
}
void SCH_HTTP_LIB_PLUGIN::EnumerateSymbolLib( wxArrayString& aSymbolNameList,
const wxString& aLibraryPath,
const STRING_UTF8_MAP* aProperties )
{
std::vector<LIB_SYMBOL*> symbols;
EnumerateSymbolLib( symbols, aLibraryPath, aProperties );
for( LIB_SYMBOL* symbol : symbols )
aSymbolNameList.Add( symbol->GetName() );
}
void SCH_HTTP_LIB_PLUGIN::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
const wxString& aLibraryPath,
const STRING_UTF8_MAP* aProperties )
{
wxCHECK_RET( m_libTable, _("httplib plugin missing library table handle!") );
ensureSettings( aLibraryPath );
ensureConnection();
if( !m_conn)
{
THROW_IO_ERROR( m_lastError );
return;
}
bool powerSymbolsOnly =
( aProperties
&& aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() );
// clear buffer
m_cachedParts.clear();
for(const HTTP_LIB_CATEGORY& category : m_conn->getCategories() )
{
std::vector<HTTP_LIB_PART> found_parts;
if( !m_conn->SelectAll( category, found_parts ) )
{
if( !m_conn->GetLastError().empty() )
{
wxString msg = wxString::Format( _( "Error retriving data from HTTP library %s: %s" ),
category.name, m_conn->GetLastError() );
THROW_IO_ERROR( msg );
}
continue;
}
// cache information for later use in LoadSymbol()
m_cachedParts.emplace( category.id, found_parts );
for( const HTTP_LIB_PART& part : found_parts )
{
wxString libIDString( part.name );
LIB_SYMBOL* symbol = loadSymbolFromPart( libIDString, category, part );
if( symbol && ( !powerSymbolsOnly || symbol->IsPower() ) )
aSymbolList.emplace_back( symbol );
}
}
}
LIB_SYMBOL* SCH_HTTP_LIB_PLUGIN::LoadSymbol( const wxString& aLibraryPath,
const wxString& aAliasName,
const STRING_UTF8_MAP* aProperties )
{
wxCHECK( m_libTable, nullptr );
ensureSettings( aLibraryPath );
ensureConnection();
if( !m_conn )
THROW_IO_ERROR( m_lastError );
std::string part_id = "";
std::string partName( aAliasName.ToUTF8() );
const HTTP_LIB_CATEGORY* foundCategory = nullptr;
HTTP_LIB_PART result;
std::vector<HTTP_LIB_CATEGORY> categories = m_conn->getCategories();
std::tuple relations = m_conn->getCachedParts()[partName];
// get the matching category
for( const HTTP_LIB_CATEGORY& categoryIter : categories )
{
std::string associatedCatID = std::get<1>( relations );
if( categoryIter.id == associatedCatID )
{
foundCategory = &categoryIter;
break;
}
}
// return Null if no category was found. This should never happen
if( foundCategory == NULL )
{
wxLogTrace( traceHTTPLib, wxT( "loadSymbol: no category found for %s" ), partName );
return NULL;
}
// get the matching query ID
for( const auto& part : m_cachedParts[foundCategory->id] )
{
if( part.id == std::get<0>( relations ) )
{
part_id = part.id;
break;
}
}
if( m_conn->SelectOne( part_id, result ) )
{
wxLogTrace( traceHTTPLib, wxT( "loadSymbol: SelectOne (%s) found in %s" ), part_id,
foundCategory->name );
}
else
{
wxLogTrace( traceHTTPLib, wxT( "loadSymbol: SelectOne (%s) failed for category %s" ),
part_id, foundCategory->name );
THROW_IO_ERROR( m_lastError );
}
wxCHECK( foundCategory, nullptr );
return loadSymbolFromPart( aAliasName, *foundCategory, result );
}
void SCH_HTTP_LIB_PLUGIN::GetSubLibraryNames( std::vector<wxString>& aNames )
{
ensureSettings( wxEmptyString );
aNames.clear();
std::set<wxString> categoryNames;
for( const HTTP_LIB_CATEGORY& categoryIter : m_conn->getCategories() )
{
if( categoryNames.count( categoryIter.name ) )
continue;
aNames.emplace_back( categoryIter.name );
categoryNames.insert( categoryIter.name );
}
}
void SCH_HTTP_LIB_PLUGIN::GetAvailableSymbolFields( std::vector<wxString>& aNames )
{
// TODO: Implement this sometime; This is currently broken...
std::copy( m_customFields.begin(), m_customFields.end(), std::back_inserter( aNames ) );
}
void SCH_HTTP_LIB_PLUGIN::GetDefaultSymbolFields( std::vector<wxString>& aNames )
{
std::copy( m_defaultShownFields.begin(), m_defaultShownFields.end(),
std::back_inserter( aNames ) );
}
void SCH_HTTP_LIB_PLUGIN::ensureSettings( const wxString& aSettingsPath )
{
auto tryLoad = [&]()
{
if( !m_settings->LoadFromFile() )
{
wxString msg = wxString::Format( _( "HTTP library settings file %s missing or invalid" ),
aSettingsPath );
THROW_IO_ERROR( msg );
}
if( m_settings->m_Source.api_version.empty() )
{
wxString msg = wxString::Format(
_( "HTTP library settings file %s is missing the API version number!" ), aSettingsPath );
THROW_IO_ERROR( msg );
}
if( m_settings->getSupportedAPIVersion() != m_settings->m_Source.api_version )
{
wxString msg = wxString::Format(
_( "HTTP library settings file %s indicates API version conflict (Settings file: %s <-> KiCad: %s)!" ),
aSettingsPath,
m_settings->m_Source.api_version,
m_settings->getSupportedAPIVersion() );
THROW_IO_ERROR( msg );
}
if( m_settings->m_Source.root_url.empty() )
{
wxString msg = wxString::Format(
_( "HTTP library settings file %s is missing the root URL!" ),
aSettingsPath );
THROW_IO_ERROR( msg );
}
// map lib source type
m_settings->m_Source.type = m_settings->get_HTTP_LIB_SOURCE_TYPE();
if( m_settings->m_Source.type == HTTP_LIB_SOURCE_TYPE::INVALID )
{
wxString msg = wxString::Format(
_( "HTTP library settings file has an invalid library type" ), aSettingsPath );
THROW_IO_ERROR( msg );
}
// make sure that the root url finishes with a forward slash
if( m_settings->m_Source.root_url.at( m_settings->m_Source.root_url.length() - 1 ) != '/' )
{
m_settings->m_Source.root_url += "/";
}
// Append api version to root URL
m_settings->m_Source.root_url += m_settings->m_Source.api_version + "/";
};
if( !m_settings && !aSettingsPath.IsEmpty() )
{
std::string path( aSettingsPath.ToUTF8() );
m_settings = std::make_unique<HTTP_LIB_SETTINGS>( path );
m_settings->SetReadOnly( true );
tryLoad();
}
else if( m_settings )
{
// If we have valid settings but no connection yet; reload settings in case user is editing
tryLoad();
}
else if( !m_settings )
{
wxLogTrace( traceHTTPLib, wxT( "ensureSettings: no settings available!" ) );
}
}
void SCH_HTTP_LIB_PLUGIN::ensureConnection()
{
wxCHECK_RET( m_settings, "Call ensureSettings before ensureConnection!" );
connect();
if( !m_conn || !m_conn->IsValidEnpoint() )
{
wxString msg = wxString::Format(
_( "Could not connect to %s. Errors: %s" ),
m_settings->m_Source.root_url, m_lastError );
THROW_IO_ERROR( msg );
}
}
void SCH_HTTP_LIB_PLUGIN::connect()
{
wxCHECK_RET( m_settings, "Call ensureSettings before connect()!" );
if( !m_conn )
{
m_conn = std::make_unique<HTTP_LIB_CONNECTION>( m_settings->m_Source, true );
if( !m_conn->IsValidEnpoint() )
{
m_lastError = m_conn->GetLastError();
return;
}
}
}
LIB_SYMBOL* SCH_HTTP_LIB_PLUGIN::loadSymbolFromPart( const wxString& aSymbolName,
const HTTP_LIB_CATEGORY& aCategory,
const HTTP_LIB_PART& aPart )
{
LIB_SYMBOL* symbol = nullptr;
LIB_SYMBOL* originalSymbol = nullptr;
LIB_ID symbolId;
std::string symbolIdStr = aPart.symbolIdStr;
// Get or Create the symbol using the found symbol
if( !symbolIdStr.empty() )
{
symbolId.Parse( symbolIdStr );
if( symbolId.IsValid() )
{
originalSymbol = m_libTable->LoadSymbol( symbolId );
}
if( originalSymbol )
{
wxLogTrace( traceHTTPLib, wxT( "loadSymbolFromPart: found original symbol '%s'" ),
symbolIdStr );
symbol = originalSymbol->Duplicate();
symbol->SetSourceLibId( symbolId );
symbol->LibId().SetSubLibraryName( aCategory.name );
}
else if( !symbolId.IsValid() )
{
wxLogTrace( traceHTTPLib,
wxT( "loadSymbolFromPart: source symbol id '%s' is invalid, "
"will create empty symbol" ),
symbolIdStr );
}
else
{
wxLogTrace( traceHTTPLib,
wxT( "loadSymbolFromPart: source symbol '%s' not found, "
"will create empty symbol" ),
symbolIdStr );
}
}
if( !symbol )
{
// Actual symbol not found: return metadata only; error will be
// indicated in the symbol chooser
symbol = new LIB_SYMBOL( aSymbolName );
symbol->LibId().SetSubLibraryName( aCategory.name );
}
LIB_FIELD* field;
for( auto& _field : aPart.fields )
{
std::string fieldName = _field.first;
std::tuple fieldProperties = _field.second;
if( fieldName == footprint_field )
{
field = &symbol->GetFootprintField();
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
}
else if( fieldName == description_field )
{
field = &symbol->GetDescriptionField();
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
}
else if( fieldName == value_field )
{
field = &symbol->GetValueField();
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
}
else if( fieldName == datasheet_field )
{
field = &symbol->GetDatasheetField();
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
}
else if( fieldName == reference_field )
{
field = &symbol->GetReferenceField();
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
}
else if( fieldName == keywords_field )
{
symbol->SetKeyWords( std::get<0>( fieldProperties ) );
}
else
{
// Generic fields
field = new LIB_FIELD( symbol->GetNextAvailableFieldId() );
field->SetName( fieldName );
field->SetText( std::get<0>( fieldProperties ) );
field->SetVisible( std::get<1>( fieldProperties ) );
symbol->AddField( field );
m_customFields.insert( fieldName );
}
}
return symbol;
}
void SCH_HTTP_LIB_PLUGIN::SaveSymbol( const wxString& aLibraryPath, const LIB_SYMBOL* aSymbol,
const STRING_UTF8_MAP* aProperties )
{
// TODO: Implement this sometime;
}

View File

@ -0,0 +1,130 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_SCH_HTTP_LIB_PLUGIN_H
#define KICAD_SCH_HTTP_LIB_PLUGIN_H
#include "http_lib/http_lib_settings.h"
#include <http_lib/http_lib_connection.h>
#include <sch_io_mgr.h>
#include <wildcards_and_files_ext.h>
/**
* A KiCad HTTP library provides both symbol and footprint metadata, so there are "shim" plugins
* on both the symbol and footprint side of things that expose the database contents to the
* schematic and board editors. The architecture of these is slightly different from the other
* plugins because the backing file is just a configuration file rather than something that
* contains symbol or footprint data.
*/
class SCH_HTTP_LIB_PLUGIN : public SCH_PLUGIN
{
public:
SCH_HTTP_LIB_PLUGIN();
virtual ~SCH_HTTP_LIB_PLUGIN();
const wxString GetName() const override
{
return wxT( "HTTP library" );
}
const PLUGIN_FILE_DESC GetLibraryFileDesc() const override
{
return PLUGIN_FILE_DESC( _HKI( "KiCad HTTP library files" ),
{ HTTPLibraryFileExtension } );
}
int GetModifyHash() const override { return 0; }
void EnumerateSymbolLib( wxArrayString& aSymbolNameList,
const wxString& aLibraryPath,
const STRING_UTF8_MAP* aProperties = nullptr ) override;
void EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
const wxString& aLibraryPath,
const STRING_UTF8_MAP* aProperties = nullptr ) override;
LIB_SYMBOL* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName,
const STRING_UTF8_MAP* aProperties = nullptr ) override;
bool SupportsSubLibraries() const override { return true; }
void GetSubLibraryNames( std::vector<wxString>& aNames ) override;
void GetAvailableSymbolFields( std::vector<wxString>& aNames ) override;
void GetDefaultSymbolFields( std::vector<wxString>& aNames ) override;
bool IsSymbolLibWritable( const wxString& aLibraryPath ) override
{
// TODO: HTTP libraries are well capabale of supporting this.
return false;
}
void SetLibTable( SYMBOL_LIB_TABLE* aTable ) override
{
m_libTable = aTable;
}
HTTP_LIB_SETTINGS* Settings() const { return m_settings.get(); }
void SaveSymbol( const wxString& aLibraryPath, const LIB_SYMBOL* aSymbol,
const STRING_UTF8_MAP* aProperties = nullptr ) override;
private:
void ensureSettings( const wxString& aSettingsPath );
void ensureConnection();
void connect();
LIB_SYMBOL* loadSymbolFromPart( const wxString& aSymbolName,
const HTTP_LIB_CATEGORY& aCategory,
const HTTP_LIB_PART& aPart );
SYMBOL_LIB_TABLE* m_libTable;
std::map<std::string, std::vector<HTTP_LIB_PART>> m_cachedParts;
/// Generally will be null if no valid connection is established
std::unique_ptr<HTTP_LIB_CONNECTION> m_conn;
std::unique_ptr<HTTP_LIB_SETTINGS> m_settings;
std::set<wxString> m_customFields;
std::set<wxString> m_defaultShownFields;
wxString m_lastError;
std::string symbol_field = "symbol";
std::string footprint_field = "footprint";
std::string description_field = "description";
std::string keywords_field = "keywords";
std::string value_field = "value";
std::string datasheet_field = "datasheet";
std::string reference_field = "reference";
};
#endif //KICAD_SCH_HTTP_LIB_PLUGIN_H

View File

@ -0,0 +1,121 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_HTTP_LIB_CONNECTION_H
#define KICAD_HTTP_LIB_CONNECTION_H
#include <any>
#include <boost/algorithm/string.hpp>
#include "http_lib/http_lib_settings.h"
#include <kicad_curl/kicad_curl_easy.h>
extern const char* const traceHTTPLib;
class HTTP_LIB_CONNECTION
{
public:
static const long DEFAULT_TIMEOUT = 10;
HTTP_LIB_CONNECTION( const HTTP_LIB_SOURCE& aSource, bool aTestConnectionNow );
~HTTP_LIB_CONNECTION();
bool IsValidEnpoint() const;
/**
* Retrieves a single part with full details from the HTTP library.
* @param aPk is the primary key of the part
* @param aResult will conatain the part if one was found
* @return true if aResult was filled; false otherwise
*/
bool SelectOne( const std::string aPartID, HTTP_LIB_PART& aFetchedPart );
/**
* Retrieves all parts from a specific category from the HTTP library.
* @param aPk is the primary key of the category
* @param aResults will be filled with all parts in that category
* @return true if the query succeeded and at least one part was found, false otherwise
*/
bool SelectAll( const HTTP_LIB_CATEGORY& aCategory, std::vector<HTTP_LIB_PART>& aParts );
std::string GetLastError() const { return m_lastError; }
std::vector<HTTP_LIB_CATEGORY> getCategories() const { return m_categories; }
auto getCachedParts() { return m_cache; }
private:
// This is clunky but at the moment the only way to free the pointer after use without KiCad crashing.
// at this point we can't use smart pointers as there is a problem with the order of how things are deleted/freed
std::unique_ptr<KICAD_CURL_EASY> createCurlEasyObject()
{
std::unique_ptr<KICAD_CURL_EASY> aCurl( new KICAD_CURL_EASY() );
//KICAD_CURL_EASY* aCurl = new KICAD_CURL_EASY();
// prepare curl
aCurl->SetHeader( "Accept", "application/json" );
aCurl->SetHeader( "Authorization", "Token " + m_token );
return aCurl;
}
bool ValidateHTTPLibraryEndpoints();
bool syncCategories();
bool checkServerResponse( std::unique_ptr<KICAD_CURL_EASY>& m_curl );
bool boolFromString( const std::any& aVal );
wxString httpErrorCodeDescription( uint16_t http_code );
std::string m_token;
std::string m_rootURL;
std::string m_user_name;
std::string m_user_pass;
HTTP_LIB_SOURCE_TYPE m_sourceType;
HTTP_LIB_PART m_cached_part;
// part.name part.id category.id
std::map<std::string, std::tuple<std::string, std::string>> m_cache;
bool m_enpointValid = false;
std::string m_lastError;
std::vector<HTTP_LIB_CATEGORY> m_categories;
std::map<std::string, std::string> m_parts;
const std::string http_endpoint_categories = "categories";
const std::string http_endpoint_parts = "parts";
const std::string http_endpoint_settings = "settings";
const std::string http_endpoint_auth = "authentication";
};
#endif //KICAD_HTTP_LIB_CONNECTION_H

View File

@ -0,0 +1,86 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Andre F. K. Iwers <iwers11@gmail.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_HTTP_LIB_SETTINGS_H
#define KICAD_HTTP_LIB_SETTINGS_H
#include <settings/json_settings.h>
enum class HTTP_LIB_SOURCE_TYPE
{
REST_API,
INVALID
};
struct HTTP_LIB_SOURCE
{
HTTP_LIB_SOURCE_TYPE type;
std::string root_url;
std::string api_version;
std::string token;
};
struct HTTP_LIB_CATEGORY
{
std::string id; ///< id of category
std::string name; ///< name of category
};
struct HTTP_LIB_PART
{
std::string id;
std::string name;
std::string symbolIdStr;
std::map<std::string, std::tuple<std::string, bool>> fields; ///< additional generic fields
};
class HTTP_LIB_SETTINGS : public JSON_SETTINGS
{
public:
HTTP_LIB_SETTINGS( const std::string& aFilename );
virtual ~HTTP_LIB_SETTINGS() {}
HTTP_LIB_SOURCE m_Source;
HTTP_LIB_SOURCE_TYPE get_HTTP_LIB_SOURCE_TYPE()
{
if( sourceType.compare( "REST_API" ) == 0 )
{
return HTTP_LIB_SOURCE_TYPE::REST_API;
}
return HTTP_LIB_SOURCE_TYPE::INVALID;
}
std::string getSupportedAPIVersion() { return api_version; }
protected:
wxString getFileExt() const override;
private:
std::string sourceType;
std::string api_version = "v1";
};
#endif //KICAD_HTTP_LIB_SETTINGS_H

View File

@ -136,6 +136,7 @@ extern const std::string HtmlFileExtension;
extern const std::string EquFileExtension;
extern const std::string HotkeyFileExtension;
extern const std::string DatabaseLibraryFileExtension;
extern const std::string HTTPLibraryFileExtension;
extern const std::string ArchiveFileExtension;