48#include <wx/mstream.h>
49#include <wx/tokenzr.h>
50#include <wx/wfstream.h>
51#include <wx/zipstrm.h>
59static const wxChar
tracePcm[] = wxT(
"KICAD_PCM" );
68 void error(
const json::json_pointer& ptr,
const json& instance,
69 const std::string& message )
override
71 throw std::invalid_argument( std::string(
"At " ) + ptr.to_string() +
", value:\n"
72 + instance.dump() +
"\n" + message +
"\n" );
78 std::function<
void(
int )> aAvailableUpdateCallback ) :
80 m_availableUpdateCallback( aAvailableUpdateCallback )
87 schema_file.AppendDir( wxS(
"schemas" ) );
96 std::ifstream installed_stream( f.GetFullPath().fn_str() );
97 nlohmann::json installed;
101 installed_stream >> installed;
103 if( installed.contains(
"packages" ) && installed[
"packages"].is_array() )
105 for(
const auto& js_entry : installed[
"packages"] )
112 catch( std::exception& e )
114 wxLogError( wxString::Format(
_(
"Error loading installed packages list: %s" ),
128 wxDir package_dir( d.GetPath() );
130 if( !package_dir.IsOpened() )
134 bool more = package_dir.GetFirst( &subdir,
"", wxDIR_DIRS | wxDIR_HIDDEN );
138 wxString actual_package_id = subdir;
139 actual_package_id.Replace(
'_',
'.' );
144 wxFileName subdir_file( d.GetPath(), subdir );
148 int stat_code = wxStat( subdir_file.GetFullPath(), &stat );
168 more = package_dir.GetNext( &subdir );
175 [&]( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
177 PreparePackage( entry.second.package );
196 const size_t aSizeLimit )
198 bool size_exceeded =
false;
200 TRANSFER_CALLBACK callback = [&](
size_t dltotal,
size_t dlnow,
size_t ultotal,
size_t ulnow )
202 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
204 size_exceeded =
true;
213 aReporter->
Report( wxString::Format(
_(
"Downloading %lld/%lld kB" ), dlnow / 1000,
226 curl.
SetURL( aUrl.ToUTF8().data() );
235 if( code != CURLE_OK )
239 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
240 wxMessageBox(
_(
"Download is too large." ) );
241 else if( code != CURLE_ABORTED_BY_CALLBACK )
255 std::stringstream repository_stream;
257 aReporter->
SetTitle(
_(
"Fetching repository" ) );
262 nlohmann::json repository_json;
266 repository_stream >> repository_json;
268 ValidateJson( repository_json, nlohmann::json_uri(
"#/definitions/Repository" ) );
272 catch(
const std::exception& e )
276 wxLogError(
_(
"Unable to parse repository: %s" ), e.what() );
277 wxLogError(
_(
"The given repository URL does not look like a valid KiCad package "
278 "repository. Please double check the URL." ) );
289 const nlohmann::json_uri& aUri )
const
297 const std::optional<wxString>& aHash,
298 std::vector<PCM_PACKAGE>& aPackages,
301 std::stringstream packages_stream;
303 aReporter->
SetTitle(
_(
"Fetching repository packages" ) );
308 wxLogError(
_(
"Unable to load repository packages url." ) );
313 std::istringstream isstream( packages_stream.str() );
315 if( aHash && !
VerifyHash( isstream, *aHash ) )
318 wxLogError(
_(
"Packages hash doesn't match. Repository may be corrupted." ) );
325 nlohmann::json packages_json = nlohmann::json::parse( packages_stream.str() );
326 ValidateJson( packages_json, nlohmann::json_uri(
"#/definitions/PackageArray" ) );
328 aPackages = packages_json[
"packages"].get<std::vector<PCM_PACKAGE>>();
330 catch( std::exception& e )
334 wxLogError( wxString::Format(
_(
"Unable to parse packages metadata:\n\n%s" ),
347 std::vector<unsigned char> bytes( picosha2::k_digest_size );
349 picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
350 bytes.begin(), bytes.end() );
351 std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
353 return aHash.compare( hex_str ) == 0;
361 wxT(
"Repository is not cached." ) );
372 const auto repository_tuple =
374 [&aRepositoryId](
const std::tuple<wxString, wxString, wxString>& t )
376 return std::get<0>( t ) == aRepositoryId;
382 wxString url = std::get<2>( *repository_tuple );
388 std::shared_ptr<PROGRESS_REPORTER> reporter;
391 reporter = std::make_shared<WX_PROGRESS_REPORTER>(
m_dialog, wxEmptyString, 1 );
398 bool packages_cache_exists =
false;
402 repo_cache.AppendDir( wxT(
"pcm" ) );
403 repo_cache.AppendDir( aRepositoryId );
404 wxFileName packages_cache( repo_cache.GetPath(), wxT(
"packages.json" ) );
406 if( repo_cache.FileExists() && packages_cache.FileExists() )
408 std::ifstream repo_stream( repo_cache.GetFullPath().fn_str() );
418 wxLogError(
_(
"Failed to parse locally stored repository.json." ) );
425 std::ifstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
429 packages_cache_stream >> js;
430 saved_repo.
package_list = js[
"packages"].get<std::vector<PCM_PACKAGE>>();
432 for(
size_t i = 0; i < saved_repo.
package_list.size(); i++ )
440 packages_cache_exists =
true;
446 wxLogError(
_(
"Packages cache for current repository is corrupted, it will "
447 "be redownloaded." ) );
453 if( !packages_cache_exists )
462 for(
size_t i = 0; i < current_repo.
package_list.size(); i++ )
468 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
470 std::ofstream repo_cache_stream( repo_cache.GetFullPath().fn_str() );
471 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
473 std::ofstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
475 js[
"packages"] = nlohmann::json( current_repo.
package_list );
476 packages_cache_stream << std::setw( 4 ) << js << std::endl;
487 wxFileName resource_file( repo_cache.GetPath(), wxT(
"resources.zip" ) );
491 if( resource_file.FileExists() )
492 mtime = wxFileModificationTime( resource_file.GetFullPath() );
496 std::ofstream resources_stream( resource_file.GetFullPath().fn_str(),
497 std::ios_base::binary );
499 reporter->SetTitle(
_(
"Downloading resources" ) );
505 resources_stream.close();
509 std::ifstream read_stream( resource_file.GetFullPath().fn_str(),
510 std::ios_base::binary );
519 wxLogError(
_(
"Resources file hash doesn't match and will not be used. "
520 "Repository may be corrupted." ) );
523 wxRemoveFile( resource_file.GetFullPath() );
529 wxRemoveFile( resource_file.GetFullPath() );
550 wxLogTrace(
tracePcm, wxS(
"Invalid/Missing repository " ) + aRepositoryId );
554 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
566 std::optional<PACKAGE_VERSION> current_version;
568 auto current_version_it =
572 return version.version == entry.current_version;
576 current_version = *current_version_it;
586 return version.version == entry.current_version;
597 return a.parsed_version > b.parsed_version;
609 int epoch = 0, major = 0, minor = 0, patch = 0;
614 wxStringTokenizer version_tokenizer( ver.
version, wxT(
"." ) );
616 major = wxAtoi( version_tokenizer.GetNextToken() );
618 if( version_tokenizer.HasMoreTokens() )
619 minor = wxAtoi( version_tokenizer.GetNextToken() );
621 if( version_tokenizer.HasMoreTokens() )
622 patch = wxAtoi( version_tokenizer.GetNextToken() );
624 ver.
parsed_version = std::make_tuple( epoch, major, minor, patch );
629 auto parse_version_tuple =
630 [](
const wxString& version,
int deflt )
632 int ver_major = deflt;
633 int ver_minor = deflt;
634 int ver_patch = deflt;
636 wxStringTokenizer tokenizer( version, wxT(
"." ) );
638 ver_major = wxAtoi( tokenizer.GetNextToken() );
640 if( tokenizer.HasMoreTokens() )
641 ver_minor = wxAtoi( tokenizer.GetNextToken() );
643 if( tokenizer.HasMoreTokens() )
644 ver_patch = wxAtoi( tokenizer.GetNextToken() );
646 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
657 wxString platform = wxT(
"windows" );
658#elif defined( __APPLE__ )
659 wxString platform = wxT(
"macos" );
661 wxString platform = wxT(
"linux" );
676 return a.parsed_version > b.parsed_version;
681const std::vector<PCM_PACKAGE>&
684 static std::vector<PCM_PACKAGE>
empty{};
702 auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
703 [&](
const auto& new_entry )
705 return new_entry.first == std::get<1>( entry );
708 if( it == aRepositories.end() )
717 for(
const std::pair<wxString, wxString>& repo : aRepositories )
719 std::string url_sha = picosha2::hash256_hex_string( repo.second );
720 m_repository_list.push_back( std::make_tuple( url_sha.substr( 0, 16 ), repo.first,
732 repo_cache.AppendDir( wxT(
"pcm" ) );
733 repo_cache.AppendDir( aRepositoryId );
735 if( repo_cache.DirExists() )
736 repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
741 const wxString& aRepositoryId )
759 if( !aRepositoryId.IsEmpty() )
775 && ( aPackage.
versions[0].runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG )
776 == PCM_PACKAGE_RUNTIME::PPR_IPC )
777 && !
Pgm().GetCommonSettings()->m_Api.enable_server )
779 if( wxMessageBox(
_(
"This plugin requires the KiCad API, which is currently "
780 "disabled in preferences. Would you like to enable it?" ),
781 _(
"Enable KiCad API" ), wxICON_QUESTION | wxYES_NO,
m_dialog )
798 const wxString& aPackageId )
835 return ver.compatible;
846 wxT(
"GetPackageUpdateVersion called on a not installed package" ) );
850 auto installed_ver_it = std::find_if(
854 return ver.version == entry.current_version;
858 wxT(
"Installed package version not found" ) );
860 auto ver_it = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
863 return ver.compatible
864 && installed_ver_it->status >= ver.status
865 && installed_ver_it->parsed_version < ver.parsed_version;
868 return ver_it == aPackage.
versions.end() ? wxString( wxT(
"" ) ) : ver_it->version;
873 return std::chrono::duration_cast<std::chrono::seconds>(
874 std::chrono::system_clock::now().time_since_epoch() ).count();
883 js[
"packages"] = nlohmann::json::array();
885 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
887 js[
"packages"].emplace_back( pair.second );
891 std::ofstream stream( f.GetFullPath().fn_str() );
893 stream << std::setw( 4 ) << js << std::endl;
895 catch( nlohmann::detail::exception& )
904 std::vector<PCM_INSTALLATION_ENTRY> v;
907 [&v](
const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
909 v.push_back( entry.second );
912 std::sort( v.begin(), v.end(),
915 return ( a.install_timestamp < b.install_timestamp )
916 || ( a.install_timestamp == b.install_timestamp
917 && a.package.identifier < b.package.identifier );
928 wxT(
"Installed package not found." ) );
930 return m_installed.at( aPackageId ).current_version;
953 const wxString& aSearchTerm )
955 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS(
" " ), wxTOKEN_STRTOK );
958 const auto find_term_matches =
959 [&](
const wxString& str )
962 wxString lower = str.Lower();
964 for(
const wxString& term : terms )
966 if( lower.Find( term ) != wxNOT_FOUND )
974 if( terms.size() == 1 && terms[0] == aPackage.
identifier )
977 if( terms.size() == 1 && find_term_matches( aPackage.
identifier ) )
981 rank += 500 * find_term_matches( aPackage.
name );
984 for(
const std::string& tag : aPackage.
tags )
985 rank += 100 * find_term_matches( wxString( tag ) );
988 rank += 10 * find_term_matches( aPackage.
description );
992 rank += find_term_matches( aPackage.
author.
name );
995 rank += 3 * find_term_matches( aPackage.
maintainer->name );
998 for(
const std::pair<const std::string, wxString>& entry : aPackage.
resources )
1000 rank += find_term_matches( entry.first );
1001 rank += find_term_matches( entry.second );
1005 if( terms.size() == 1 && terms[0] == aPackage.
license )
1012std::unordered_map<wxString, wxBitmap>
1015 std::unordered_map<wxString, wxBitmap> bitmaps;
1018 resources_file.AppendDir( wxT(
"pcm" ) );
1019 resources_file.AppendDir( aRepositoryId );
1021 if( !resources_file.FileExists() )
1024 wxFFileInputStream stream( resources_file.GetFullPath() );
1025 wxZipInputStream
zip( stream );
1027 if( !
zip.IsOk() ||
zip.GetTotalEntries() == 0 )
1030 for( wxArchiveEntry* entry =
zip.GetNextEntry(); entry; entry =
zip.GetNextEntry() )
1032 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1035 if( path_parts.size() != 2 || path_parts[1] != wxT(
"icon.png" ) )
1040 wxMemoryInputStream image_stream(
zip, entry->GetSize() );
1041 wxImage
image( image_stream, wxBITMAP_TYPE_PNG );
1042 bitmaps.emplace( path_parts[0], wxBitmap(
image ) );
1047 wxLogTrace( wxT(
"Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1048 resources_file.GetFullPath() );
1058 std::unordered_map<wxString, wxBitmap> bitmaps;
1061 resources_dir_fn.AppendDir( wxT(
"resources" ) );
1062 wxDir resources_dir( resources_dir_fn.GetPath() );
1064 if( !resources_dir.IsOpened() )
1068 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1072 wxFileName icon( resources_dir_fn.GetPath(), wxT(
"icon.png" ) );
1073 icon.AppendDir( subdir );
1075 if( icon.FileExists() )
1077 wxString actual_package_id = subdir;
1078 actual_package_id.Replace(
'_',
'.' );
1082 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1083 bitmaps.emplace( actual_package_id, bitmap );
1088 wxLogTrace( wxT(
"Error loading png bitmap from %s" ), icon.GetFullPath() );
1092 more = resources_dir.GetNext( &subdir );
1136 std::unordered_set<wxString> repo_ids;
1138 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
1140 if( !pair.second.pinned )
1141 repo_ids.insert( pair.second.repository_id );
1147 if( repo_ids.count( repository_id ) == 0 )
1151 _(
"Fetching repository..." ) );
1162 int availableUpdateCount = 0;
1165 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
1177 availableUpdateCount++;
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
const std::tuple< int, int, int > & GetMajorMinorPatchTuple()
Get the build version numbers as a tuple.
std::shared_ptr< BACKGROUND_JOB > Create(const wxString &aName)
Creates a background job with the given name.
void Remove(std::shared_ptr< BACKGROUND_JOB > job)
Removes the given background job from any lists and frees it.
EDA_BASE_FRAME * ParentFrame() const
Closes the window, asks user confirmation if there are pending actions.
int Perform()
Equivalent to curl_easy_perform.
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
bool SetOutputStream(const std::ostream *aOutput)
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
virtual void CommonSettingsChanged(int aFlags=0)
Call CommonSettingsChanged() on all KIWAY_PLAYERs.
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
virtual COMMON_SETTINGS * GetCommonSettings() const
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
time_t getCurrentTimestamp() const
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_validator
void ValidateJson(const nlohmann::json &aJson, const nlohmann::json_uri &aUri=nlohmann::json_uri("#")) const
Validates json against a specific definition in the PCM schema.
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
int GetPackageSearchRank(const PCM_PACKAGE &aPackage, const wxString &aSearchTerm)
Get the approximate measure of how much given package matches the search term.
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
void SaveInstalledPackages()
Saves metadata of installed packages to disk.
~PLUGIN_CONTENT_MANAGER()
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
PLUGIN_CONTENT_MANAGER(std::function< void(int)> aAvailableUpdateCallbac)
bool DownloadToStream(const wxString &aUrl, std::ostream *aOutput, PROGRESS_REPORTER *aReporter, const size_t aSizeLimit=DEFAULT_DOWNLOAD_MEM_LIMIT)
Downloads url to an output stream.
std::thread m_updateThread
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
bool IsPackagePinned(const wxString &aPackageId) const
Returns pinned status of a package.
static const std::tuple< int, int, int > m_kicad_version
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
STRING_TUPLE_LIST m_repository_list
bool fetchPackages(const wxString &aUrl, const std::optional< wxString > &aHash, std::vector< PCM_PACKAGE > &aPackages, PROGRESS_REPORTER *aReporter)
Downloads packages metadata to in memory stream, verifies hash and attempts to parse it.
std::function< void(int)> m_availableUpdateCallback
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
void ReadEnvVar()
Stores 3rdparty path from environment variables.
A progress reporter interface for use in multi-threaded environments.
virtual bool IsCancelled() const =0
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void SetTitle(const wxString &aTitle)=0
Change the title displayed on the window caption.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
void error(const json::json_pointer &ptr, const json &instance, const std::string &message) override
static bool empty(const wxTextEntryBase *aCtrl)
Base window classes and related definitions.
Functions related to environment variables, including help functions.
static const wxChar tracePcm[]
Flag to enable PCM debugging output.
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
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.
std::vector< std::pair< wxString, wxString > > STRING_PAIR_LIST
const std::unordered_set< wxString > PCM_PACKAGE_DIRECTORIES({ "plugins", "footprints", "3dmodels", "symbols", "resources", "colors", "templates", "scripts" })
< Contains list of all valid directories that get extracted from a package archive
PGM_BASE & Pgm()
The global program "get" accessor.
< Package version metadata Package metadata
PCM_PACKAGE_VERSION_STATUS status
std::optional< wxString > kicad_version_max
std::optional< int > version_epoch
std::vector< std::string > platforms
std::tuple< int, int, int, int > parsed_version
uint64_t install_timestamp
Repository reference to a resource.
wxString description_full
std::vector< std::string > tags
std::optional< PCM_CONTACT > maintainer
std::vector< PACKAGE_VERSION > versions
Package installation entry.
PCM_RESOURCE_REFERENCE packages
std::vector< PCM_PACKAGE > package_list
std::optional< PCM_RESOURCE_REFERENCE > resources
std::unordered_map< wxString, size_t > package_map
std::optional< wxString > sha256
uint64_t update_timestamp
UPDATE_CANCELLER(std::shared_ptr< BACKGROUND_JOB > &aJob)
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().