KiCad PCB EDA Suite
PLUGIN_CONTENT_MANAGER Class Reference

Main class of Plugin and Content Manager subsystem. More...

#include <pcm.h>

Public Member Functions

 PLUGIN_CONTENT_MANAGER (std::function< void(int)> aAvailableUpdateCallback, std::function< void(const wxString)> aStatusCallback)
 
 ~PLUGIN_CONTENT_MANAGER ()
 
void SaveInstalledPackages ()
 Saves metadata of installed packages to disk. More...
 
bool FetchRepository (const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
 Fetches repository metadata from given url. More...
 
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. More...
 
bool VerifyHash (std::istream &aStream, const wxString &aHash) const
 Verifies SHA256 hash of a binary stream. More...
 
void SetRepositoryList (const STRING_PAIR_LIST &aRepositories)
 Set list of repositories. More...
 
void DiscardRepositoryCache (const wxString &aRepositoryId)
 Discard in-memory and on-disk cache of a repository. More...
 
void MarkInstalled (const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
 Mark package as installed. More...
 
void MarkUninstalled (const PCM_PACKAGE &aPackage)
 Mark package as uninstalled. More...
 
const STRING_TUPLE_LISTGetRepositoryList () const
 Get list of repositories. More...
 
const bool CacheRepository (const wxString &aRepositoryId)
 Cache specified repository packages and other metadata. More...
 
const std::vector< PCM_PACKAGE > & GetRepositoryPackages (const wxString &aRepositoryId) const
 Get the packages metadata from a previously cached repository. More...
 
const std::vector< PCM_INSTALLATION_ENTRYGetInstalledPackages () const
 Get list of installed packages. More...
 
const wxString & GetInstalledPackageVersion (const wxString &aPackageId) const
 Get the current version of an installed package. More...
 
const wxString & Get3rdPartyPath () const
 
PCM_PACKAGE_STATE GetPackageState (const wxString &aRepositoryId, const wxString &aPackageId)
 Get current state of the package. More...
 
bool IsPackagePinned (const wxString &aPackageId) const
 Returns pinned status of a package. More...
 
void SetPinned (const wxString &aPackageId, const bool aPinned)
 Set the pinned status of a package. More...
 
const wxString GetPackageUpdateVersion (const PCM_PACKAGE &aPackage)
 Get the preferred package update version or empty string if there is none. More...
 
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. More...
 
int GetPackageSearchRank (const PCM_PACKAGE &aPackage, const wxString &aSearchTerm)
 Get the approximate measure of how much given package matches the search term. More...
 
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps (const wxString &aRepositoryId)
 Get the icon bitmaps for repository packages. More...
 
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps ()
 Get the icon bitmaps for installed packages. More...
 
void SetDialogWindow (wxWindow *aDialog)
 Set the Dialog Window. More...
 
void RunBackgroundUpdate ()
 Runs a background update thread that checks for new package versions. More...
 
void StopBackgroundUpdate ()
 Interrupts and joins() the update thread. More...
 
void ReadEnvVar ()
 Stores 3rdparty path from environment variables. More...
 

Private Member Functions

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. More...
 
const PCM_REPOSITORYgetCachedRepository (const wxString &aRepositoryId) const
 Get the cached repository metadata. More...
 
void updateInstalledPackagesMetadata (const wxString &aRepositoryId)
 Updates metadata of installed packages from freshly fetched repo. More...
 
time_t getCurrentTimestamp () const
 

Static Private Member Functions

static void preparePackage (PCM_PACKAGE &aPackage)
 Parses version strings and calculates compatibility. More...
 

Private Attributes

wxWindow * m_dialog
 
nlohmann::json_schema::json_validator m_schema_validator
 
wxString m_3rdparty_path
 
wxString m_cache_path
 
std::unordered_map< wxString, PCM_REPOSITORYm_repository_cache
 
STRING_TUPLE_LIST m_repository_list
 
std::map< wxString, PCM_INSTALLATION_ENTRYm_installed
 
std::function< void(int)> m_availableUpdateCallback
 
std::function< void(const wxString)> m_statusCallback
 
std::thread m_updateThread
 
std::shared_ptr< STATUS_TEXT_REPORTERm_statusReporter
 

Static Private Attributes

static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT = 10 * 1024 * 1024
 < Default download limit of 10 Mb to not use too much memory More...
 
static const std::tuple< int, int, int > m_kicad_version
 

Detailed Description

Main class of Plugin and Content Manager subsystem.

This class handles logistics of repository management, caching, json validation, tracking installed packages and provides some utility methods.

Repository caching is done in $KICADX_3RD_PARTY/cache directory with each repository storing it's metadata, packages and optionally resource files under it's id subdirectory.

Repository id is a prefix of sha256 of it's main url.

JSON schema file is expected to be in $KICAD_DATA/schemas directory

Installed packages are stored in <user_settings>/installed_packages.json If that file is missing PCM will try to reconstruct it from existing directory structure inside $KICADX_3RD_PARTY but package descriptions and other metadata will be lost.

Definition at line 101 of file pcm.h.

Constructor & Destructor Documentation

◆ PLUGIN_CONTENT_MANAGER()

PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER ( std::function< void(int)>  aAvailableUpdateCallback,
std::function< void(const wxString)>  aStatusCallback 
)

Definition at line 97 of file pcm.cpp.

99 :
100 m_dialog( nullptr ),
101 m_availableUpdateCallback( aAvailableUpdateCallback ),
102 m_statusCallback( aStatusCallback )
103{
104 ReadEnvVar();
105
106 // Read and store pcm schema
107 wxFileName schema_file( PATHS::GetStockDataPath( true ), wxS( "pcm.v1.schema.json" ) );
108 schema_file.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
109 schema_file.AppendDir( wxS( "schemas" ) );
110
111 std::ifstream schema_stream( schema_file.GetFullPath().ToUTF8() );
112 nlohmann::json schema;
113
114 try
115 {
116 schema_stream >> schema;
117 m_schema_validator.set_root_schema( schema );
118 }
119 catch( std::exception& e )
120 {
121 if( !schema_file.FileExists() )
122 {
123 wxLogError( wxString::Format( _( "schema file '%s' not found" ),
124 schema_file.GetFullPath() ) );
125 }
126 else
127 {
128 wxLogError( wxString::Format( _( "Error loading schema: %s" ), e.what() ) );
129 }
130 }
131
132 // Load currently installed packages
133 wxFileName f( SETTINGS_MANAGER::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
134
135 if( f.FileExists() )
136 {
137 std::ifstream installed_stream( f.GetFullPath().ToUTF8() );
138 nlohmann::json installed;
139
140 try
141 {
142 installed_stream >> installed;
143
144 if( installed.contains( "packages" ) && installed["packages"].is_array() )
145 {
146 for( const auto& js_entry : installed["packages"] )
147 {
148 PCM_INSTALLATION_ENTRY entry = js_entry.get<PCM_INSTALLATION_ENTRY>();
149 m_installed.emplace( entry.package.identifier, entry );
150 }
151 }
152 }
153 catch( std::exception& e )
154 {
155 wxLogError( wxString::Format( _( "Error loading installed packages list: %s" ),
156 e.what() ) );
157 }
158 }
159
160 // As a fall back populate installed from names of directories
161
162 for( const wxString& dir : PCM_PACKAGE_DIRECTORIES )
163 {
164 wxFileName d( m_3rdparty_path, wxEmptyString );
165 d.AppendDir( dir );
166
167 if( d.DirExists() )
168 {
169 wxDir package_dir( d.GetPath() );
170
171 if( !package_dir.IsOpened() )
172 continue;
173
174 wxString subdir;
175 bool more = package_dir.GetFirst( &subdir, "", wxDIR_DIRS | wxDIR_HIDDEN );
176
177 while( more )
178 {
179 wxString actual_package_id = subdir;
180 actual_package_id.Replace( '_', '.' );
181
182 if( m_installed.find( actual_package_id ) == m_installed.end() )
183 {
185 wxFileName subdir_file( d.GetPath(), subdir );
186
187 // wxFileModificationTime bugs out on windows for directories
188 wxStructStat stat;
189 int stat_code = wxStat( subdir_file.GetFullPath(), &stat );
190
191 entry.package.name = subdir;
192 entry.package.identifier = actual_package_id;
193 entry.current_version = "0.0";
194 entry.repository_name = wxT( "<unknown>" );
195
196 if( stat_code == 0 )
197 entry.install_timestamp = stat.st_mtime;
198
199 PACKAGE_VERSION version;
200 version.version = "0.0";
201 version.status = PVS_STABLE;
203
204 entry.package.versions.emplace_back( version );
205
206 m_installed.emplace( actual_package_id, entry );
207 }
208
209 more = package_dir.GetNext( &subdir );
210 }
211 }
212 }
213
214 // Calculate package compatibility
215 std::for_each( m_installed.begin(), m_installed.end(),
216 [&]( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
217 {
218 preparePackage( entry.second.package );
219 } );
220}
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition: paths.cpp:150
wxWindow * m_dialog
Definition: pcm.h:393
std::function< void(const wxString)> m_statusCallback
Definition: pcm.h:403
wxString m_3rdparty_path
Definition: pcm.h:395
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
Definition: pcm.h:400
nlohmann::json_schema::json_validator m_schema_validator
Definition: pcm.h:394
std::function< void(int)> m_availableUpdateCallback
Definition: pcm.h:402
void ReadEnvVar()
Stores 3rdparty path from environment variables.
Definition: pcm.cpp:223
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
#define _(s)
nlohmann::json json
Definition: gerbview.cpp:44
const std::unordered_set< wxString > PCM_PACKAGE_DIRECTORIES({ "plugins", "footprints", "3dmodels", "symbols", "resources", "colors", })
< Contains list of all valid directories that get extracted from a package archive
@ PVS_STABLE
Definition: pcm_data.h:53
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
< Package version metadata Package metadata
Definition: pcm_data.h:74
wxString version
Definition: pcm_data.h:75
PCM_PACKAGE_VERSION_STATUS status
Definition: pcm_data.h:81
wxString kicad_version
Definition: pcm_data.h:83
Definition: pcm_data.h:138
wxString repository_name
Definition: pcm_data.h:142
PCM_PACKAGE package
Definition: pcm_data.h:139
uint64_t install_timestamp
Definition: pcm_data.h:143
wxString current_version
Definition: pcm_data.h:140
wxString identifier
Definition: pcm_data.h:99
wxString name
Definition: pcm_data.h:96
std::vector< PACKAGE_VERSION > versions
Definition: pcm_data.h:107
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition: wx_filename.h:38

References _, PCM_INSTALLATION_ENTRY::current_version, FN_NORMALIZE_FLAGS, Format(), GetMajorMinorVersion(), PATHS::GetStockDataPath(), SETTINGS_MANAGER::GetUserSettingsPath(), PCM_PACKAGE::identifier, PCM_INSTALLATION_ENTRY::install_timestamp, PACKAGE_VERSION::kicad_version, m_3rdparty_path, m_installed, m_schema_validator, PCM_PACKAGE::name, PCM_INSTALLATION_ENTRY::package, PCM_PACKAGE_DIRECTORIES(), PVS_STABLE, ReadEnvVar(), PCM_INSTALLATION_ENTRY::repository_name, PACKAGE_VERSION::status, PACKAGE_VERSION::version, and PCM_PACKAGE::versions.

◆ ~PLUGIN_CONTENT_MANAGER()

PLUGIN_CONTENT_MANAGER::~PLUGIN_CONTENT_MANAGER ( )

Definition at line 1207 of file pcm.cpp.

1208{
1209 // By the time object is being destroyed the thread should be
1210 // stopped already but just in case do it here too.
1212}
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
Definition: pcm.cpp:1197

References StopBackgroundUpdate().

Member Function Documentation

◆ CacheRepository()

const bool PLUGIN_CONTENT_MANAGER::CacheRepository ( const wxString &  aRepositoryId)

Cache specified repository packages and other metadata.

This method fetches latest repository metadata, checks if there is cache on disk, compares it's last update timestamp to repository metadata and redownloads packages list if necessary. Then it checks sha256 if provided and parses the package list. Parsed packages metadata is stored in memory.

Process is repeated with resources file except it is just stored on disk and any errors at this stage are ignored.

Parameters
aRepositoryIdid of the repository to cache
Returns
true if packages metadata was cached successfully

Definition at line 414 of file pcm.cpp.

415{
416 if( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end() )
417 return true;
418
419 const auto repository_tuple =
420 std::find_if( m_repository_list.begin(), m_repository_list.end(),
421 [&aRepositoryId]( const std::tuple<wxString, wxString, wxString>& t )
422 {
423 return std::get<0>( t ) == aRepositoryId;
424 } );
425
426 if( repository_tuple == m_repository_list.end() )
427 return false;
428
429 wxString url = std::get<2>( *repository_tuple );
430
432 PCM_REPOSITORY current_repo;
433
434 std::shared_ptr<PROGRESS_REPORTER> reporter;
435
436 if( m_dialog )
437 reporter = std::make_shared<WX_PROGRESS_REPORTER>( m_dialog, wxEmptyString, 1 );
438 else
439 reporter = m_statusReporter;
440
441 if( !FetchRepository( url, current_repo, reporter.get() ) )
442 return false;
443
444 bool packages_cache_exists = false;
445
446 // First load repository data from local filesystem if available.
447 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), wxT( "repository.json" ) );
448 repo_cache.AppendDir( wxT( "pcm" ) );
449 repo_cache.AppendDir( aRepositoryId );
450 wxFileName packages_cache( repo_cache.GetPath(), wxT( "packages.json" ) );
451
452 if( repo_cache.FileExists() && packages_cache.FileExists() )
453 {
454 std::ifstream repo_stream( repo_cache.GetFullPath().ToUTF8() );
455 PCM_REPOSITORY saved_repo;
456 try
457 {
458 repo_stream >> js;
459 saved_repo = js.get<PCM_REPOSITORY>();
460 }
461 catch( ... )
462 {
463 if( m_dialog )
464 wxLogError( _( "Failed to parse locally stored repository.json." ) );
465 }
466
467 if( saved_repo.packages.update_timestamp == current_repo.packages.update_timestamp )
468 {
469 // Cached repo is up to date, use data on disk
470 js.clear();
471 std::ifstream packages_cache_stream( packages_cache.GetFullPath().ToUTF8() );
472
473 try
474 {
475 packages_cache_stream >> js;
476 saved_repo.package_list = js["packages"].get<std::vector<PCM_PACKAGE>>();
477
478 for( size_t i = 0; i < saved_repo.package_list.size(); i++ )
479 {
480 preparePackage( saved_repo.package_list[i] );
481 saved_repo.package_map[saved_repo.package_list[i].identifier] = i;
482 }
483
484 m_repository_cache[aRepositoryId] = std::move( saved_repo );
485
486 packages_cache_exists = true;
487 }
488 catch( ... )
489 {
490 if( m_dialog )
491 {
492 wxLogError( _( "Packages cache for current repository is corrupted, it will "
493 "be redownloaded." ) );
494 }
495 }
496 }
497 }
498
499 if( !packages_cache_exists )
500 {
501 // Cache doesn't exist or is out of date
502 if( !fetchPackages( current_repo.packages.url, current_repo.packages.sha256,
503 current_repo.package_list, reporter.get() ) )
504 {
505 return false;
506 }
507
508 for( size_t i = 0; i < current_repo.package_list.size(); i++ )
509 {
510 preparePackage( current_repo.package_list[i] );
511 current_repo.package_map[current_repo.package_list[i].identifier] = i;
512 }
513
514 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
515
516 std::ofstream repo_cache_stream( repo_cache.GetFullPath().ToUTF8() );
517 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
518
519 std::ofstream packages_cache_stream( packages_cache.GetFullPath().ToUTF8() );
520 js.clear();
521 js["packages"] = nlohmann::json( current_repo.package_list );
522 packages_cache_stream << std::setw( 4 ) << js << std::endl;
523
524 m_repository_cache[aRepositoryId] = std::move( current_repo );
525 }
526
527 if( current_repo.resources )
528 {
529 // Check resources file date, redownload if needed
530 PCM_RESOURCE_REFERENCE& resources = *current_repo.resources;
531
532 wxFileName resource_file( repo_cache.GetPath(), wxT( "resources.zip" ) );
533
534 time_t mtime = 0;
535
536 if( resource_file.FileExists() )
537 mtime = wxFileModificationTime( resource_file.GetFullPath() );
538
539 if( mtime + 600 < getCurrentTimestamp() && mtime < (time_t) resources.update_timestamp )
540 {
541 std::ofstream resources_stream( resource_file.GetFullPath().ToUTF8(),
542 std::ios_base::binary );
543
544 reporter->SetTitle( _( "Downloading resources" ) );
545
546 // 100 Mb resource file limit
547 bool success = DownloadToStream( resources.url, &resources_stream, reporter.get(),
548 100 * 1024 * 1024 );
549
550 resources_stream.close();
551
552 if( success )
553 {
554 std::ifstream read_stream( resource_file.GetFullPath().ToUTF8(),
555 std::ios_base::binary );
556
557
558 if( resources.sha256 && !VerifyHash( read_stream, *resources.sha256 ) )
559 {
560 read_stream.close();
561
562 if( m_dialog )
563 {
564 wxLogError( _( "Resources file hash doesn't match and will not be used. "
565 "Repository may be corrupted." ) );
566 }
567
568 wxRemoveFile( resource_file.GetFullPath() );
569 }
570 }
571 else
572 {
573 // Not critical, just clean up the file
574 wxRemoveFile( resource_file.GetFullPath() );
575 }
576 }
577 }
578
579 updateInstalledPackagesMetadata( aRepositoryId );
580
581 return true;
582}
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
Definition: paths.cpp:321
time_t getCurrentTimestamp() const
Definition: pcm.cpp:903
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
Definition: pcm.h:397
std::shared_ptr< STATUS_TEXT_REPORTER > m_statusReporter
Definition: pcm.h:406
static void preparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
Definition: pcm.cpp:649
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.
Definition: pcm.cpp:236
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
Definition: pcm.cpp:585
STRING_TUPLE_LIST m_repository_list
Definition: pcm.h:398
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.
Definition: pcm.cpp:343
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
Definition: pcm.cpp:294
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
Definition: pcm.cpp:392
Package installation entry.
Definition: pcm_data.h:122
PCM_RESOURCE_REFERENCE packages
Definition: pcm_data.h:124
std::vector< PCM_PACKAGE > package_list
Definition: pcm_data.h:130
std::optional< PCM_RESOURCE_REFERENCE > resources
Definition: pcm_data.h:125
std::unordered_map< wxString, size_t > package_map
Definition: pcm_data.h:132
Repository metadata.
Definition: pcm_data.h:113
std::optional< wxString > sha256
Definition: pcm_data.h:115
uint64_t update_timestamp
Definition: pcm_data.h:116

References _, DownloadToStream(), fetchPackages(), FetchRepository(), getCurrentTimestamp(), PATHS::GetUserCachePath(), m_dialog, m_repository_cache, m_repository_list, m_statusReporter, PCM_REPOSITORY::package_list, PCM_REPOSITORY::package_map, PCM_REPOSITORY::packages, preparePackage(), PCM_REPOSITORY::resources, PCM_RESOURCE_REFERENCE::sha256, PCM_RESOURCE_REFERENCE::update_timestamp, updateInstalledPackagesMetadata(), PCM_RESOURCE_REFERENCE::url, and VerifyHash().

Referenced by GetPackageState().

◆ DiscardRepositoryCache()

void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache ( const wxString &  aRepositoryId)

Discard in-memory and on-disk cache of a repository.

Parameters
aRepositoryIdid of the repository

Definition at line 773 of file pcm.cpp.

774{
775 if( m_repository_cache.count( aRepositoryId ) > 0 )
776 m_repository_cache.erase( aRepositoryId );
777
778 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), "" );
779 repo_cache.AppendDir( wxT( "pcm" ) );
780 repo_cache.AppendDir( aRepositoryId );
781
782 if( repo_cache.DirExists() )
783 repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
784}

References PATHS::GetUserCachePath(), and m_repository_cache.

Referenced by SetRepositoryList().

◆ DownloadToStream()

bool PLUGIN_CONTENT_MANAGER::DownloadToStream ( const wxString &  aUrl,
std::ostream *  aOutput,
PROGRESS_REPORTER aReporter,
const size_t  aSizeLimit = DEFAULT_DOWNLOAD_MEM_LIMIT 
)

Downloads url to an output stream.

Parameters
aUrlURL to download
aOutputoutput stream
aReporterprogress dialog to use
aSizeLimitmaximum download size, 0 for unlimited
Returns
true if download was successful
false if download failed or was too large

Definition at line 236 of file pcm.cpp.

239{
240 bool size_exceeded = false;
241
242 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
243 {
244 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
245 {
246 size_exceeded = true;
247
248 // Non zero return means abort.
249 return true;
250 }
251
252 if( dltotal > 1000 )
253 {
254 aReporter->SetCurrentProgress( dlnow / (double) dltotal );
255 aReporter->Report( wxString::Format( _( "Downloading %lld/%lld kB" ), dlnow / 1000,
256 dltotal / 1000 ) );
257 }
258 else
259 {
260 aReporter->SetCurrentProgress( 0.0 );
261 }
262
263 return !aReporter->KeepRefreshing();
264 };
265
266 KICAD_CURL_EASY curl;
267 curl.SetOutputStream( aOutput );
268 curl.SetURL( aUrl.ToUTF8().data() );
269 curl.SetFollowRedirects( true );
270 curl.SetTransferCallback( callback, 250000L );
271
272 int code = curl.Perform();
273
274 if( !aReporter->IsCancelled() )
275 aReporter->SetCurrentProgress( 1.0 );
276
277 if( code != CURLE_OK )
278 {
279 if( m_dialog )
280 {
281 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
282 wxMessageBox( _( "Download is too large." ) );
283 else if( code != CURLE_ABORTED_BY_CALLBACK )
284 wxLogError( wxString( curl.GetErrorText( code ) ) );
285 }
286
287 return false;
288 }
289
290 return true;
291}
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.
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 SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.

References _, Format(), KICAD_CURL_EASY::GetErrorText(), PROGRESS_REPORTER::IsCancelled(), PROGRESS_REPORTER::KeepRefreshing(), m_dialog, KICAD_CURL_EASY::Perform(), PROGRESS_REPORTER::Report(), PROGRESS_REPORTER::SetCurrentProgress(), KICAD_CURL_EASY::SetFollowRedirects(), KICAD_CURL_EASY::SetOutputStream(), KICAD_CURL_EASY::SetTransferCallback(), and KICAD_CURL_EASY::SetURL().

Referenced by CacheRepository(), fetchPackages(), and FetchRepository().

◆ fetchPackages()

bool PLUGIN_CONTENT_MANAGER::fetchPackages ( const wxString &  aUrl,
const std::optional< wxString > &  aHash,
std::vector< PCM_PACKAGE > &  aPackages,
PROGRESS_REPORTER aReporter 
)
private

Downloads packages metadata to in memory stream, verifies hash and attempts to parse it.

Parameters
aUrlURL of the packages metadata
aHashoptional sha256 hash
aPackagesresulting packages metadata list
aReporterprogress dialog to use for download
Returns
true if packages were successfully downloaded, verified and parsed

Definition at line 343 of file pcm.cpp.

347{
348 std::stringstream packages_stream;
349
350 aReporter->SetTitle( _( "Fetching repository packages" ) );
351
352 if( !DownloadToStream( aUrl, &packages_stream, aReporter ) )
353 {
354 if( m_dialog )
355 wxLogError( _( "Unable to load repository packages url." ) );
356
357 return false;
358 }
359
360 std::istringstream isstream( packages_stream.str() );
361
362 if( aHash && !VerifyHash( isstream, *aHash ) )
363 {
364 if( m_dialog )
365 wxLogError( _( "Packages hash doesn't match. Repository may be corrupted." ) );
366
367 return false;
368 }
369
370 try
371 {
372 nlohmann::json packages_json = nlohmann::json::parse( packages_stream.str() );
373 ValidateJson( packages_json, nlohmann::json_uri( "#/definitions/PackageArray" ) );
374
375 aPackages = packages_json["packages"].get<std::vector<PCM_PACKAGE>>();
376 }
377 catch( std::exception& e )
378 {
379 if( m_dialog )
380 {
381 wxLogError( wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ),
382 e.what() ) );
383 }
384
385 return false;
386 }
387
388 return true;
389}
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.
Definition: pcm.cpp:335
virtual void SetTitle(const wxString &aTitle)=0
Change the title displayed on the window caption.
bool parse(std::istream &aStream, bool aVerbose)
Parse a PCB or footprint file from the given input stream.

References _, DownloadToStream(), Format(), m_dialog, parse(), PROGRESS_REPORTER::SetTitle(), ValidateJson(), and VerifyHash().

Referenced by CacheRepository().

◆ FetchRepository()

bool PLUGIN_CONTENT_MANAGER::FetchRepository ( const wxString &  aUrl,
PCM_REPOSITORY aRepository,
PROGRESS_REPORTER aReporter 
)

Fetches repository metadata from given url.

Parameters
aUrlURL of the repository
aRepositoryfetched repository metadata
aReporterprogress reporter dialog to use for download
Returns
true if successful
false if URL could not be downloaded or result could not be parsed

Definition at line 294 of file pcm.cpp.

296{
297 std::stringstream repository_stream;
298
299 aReporter->SetTitle( _( "Fetching repository" ) );
300
301 if( !DownloadToStream( aUrl, &repository_stream, aReporter, 20480 ) )
302 {
303 if( m_dialog )
304 wxLogError( _( "Unable to load repository url" ) );
305
306 return false;
307 }
308
309 nlohmann::json repository_json;
310
311 try
312 {
313 repository_stream >> repository_json;
314
315 ValidateJson( repository_json, nlohmann::json_uri( "#/definitions/Repository" ) );
316
317 aRepository = repository_json.get<PCM_REPOSITORY>();
318 }
319 catch( const std::exception& e )
320 {
321 if( m_dialog )
322 {
323 wxLogError( wxString::Format( _( "Unable to parse repository: %s" ), e.what() ) );
324 wxLogError( _( "The given repository URL does not look like a valid KiCad package "
325 "repository. Please double check the URL." ) );
326 }
327
328 return false;
329 }
330
331 return true;
332}

References _, DownloadToStream(), Format(), m_dialog, PROGRESS_REPORTER::SetTitle(), and ValidateJson().

Referenced by CacheRepository().

◆ Get3rdPartyPath()

const wxString & PLUGIN_CONTENT_MANAGER::Get3rdPartyPath ( ) const
inline

Definition at line 232 of file pcm.h.

232{ return m_3rdparty_path; };

References m_3rdparty_path.

◆ getCachedRepository()

const PCM_REPOSITORY & PLUGIN_CONTENT_MANAGER::getCachedRepository ( const wxString &  aRepositoryId) const
private

Get the cached repository metadata.

Parameters
aRepositoryIdid of the repository
Returns
const PCM_REPOSITORY&

Definition at line 405 of file pcm.cpp.

406{
407 wxASSERT_MSG( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end(),
408 wxT( "Repository is not cached." ) );
409
410 return m_repository_cache.at( aRepositoryId );
411}

References m_repository_cache.

Referenced by GetPackageState(), GetRepositoryPackages(), MarkInstalled(), and updateInstalledPackagesMetadata().

◆ getCurrentTimestamp()

time_t PLUGIN_CONTENT_MANAGER::getCurrentTimestamp ( ) const
private

Definition at line 903 of file pcm.cpp.

904{
905 return std::chrono::duration_cast<std::chrono::seconds>(
906 std::chrono::system_clock::now().time_since_epoch() ).count();
907}

Referenced by CacheRepository(), and MarkInstalled().

◆ GetInstalledPackageBitmaps()

std::unordered_map< wxString, wxBitmap > PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps ( )

Get the icon bitmaps for installed packages.

Icons for installed packages are taken from package extracted files in $KICADX_3RD_PARTY/resources/<packageid> directories

Returns
map of package id -> bitmap

Definition at line 1088 of file pcm.cpp.

1089{
1090 std::unordered_map<wxString, wxBitmap> bitmaps;
1091
1092 wxFileName resources_dir_fn( m_3rdparty_path, wxEmptyString );
1093 resources_dir_fn.AppendDir( wxT( "resources" ) );
1094 wxDir resources_dir( resources_dir_fn.GetPath() );
1095
1096 if( !resources_dir.IsOpened() )
1097 return bitmaps;
1098
1099 wxString subdir;
1100 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1101
1102 while( more )
1103 {
1104 wxFileName icon( resources_dir_fn.GetPath(), wxT( "icon.png" ) );
1105 icon.AppendDir( subdir );
1106
1107 if( icon.FileExists() )
1108 {
1109 wxString actual_package_id = subdir;
1110 actual_package_id.Replace( '_', '.' );
1111
1112 try
1113 {
1114 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1115 bitmaps.emplace( actual_package_id, bitmap );
1116 }
1117 catch( ... )
1118 {
1119 // Log and ignore
1120 wxLogTrace( wxT( "Error loading png bitmap from %s" ), icon.GetFullPath() );
1121 }
1122 }
1123
1124 more = resources_dir.GetNext( &subdir );
1125 }
1126
1127 return bitmaps;
1128}

References m_3rdparty_path.

◆ GetInstalledPackages()

const std::vector< PCM_INSTALLATION_ENTRY > PLUGIN_CONTENT_MANAGER::GetInstalledPackages ( ) const

Get list of installed packages.

Returns
vector of PCM_INSTALLATION_ENTRY objects

Definition at line 934 of file pcm.cpp.

935{
936 std::vector<PCM_INSTALLATION_ENTRY> v;
937
938 std::for_each( m_installed.begin(), m_installed.end(),
939 [&v]( const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
940 {
941 v.push_back( entry.second );
942 } );
943
944 std::sort( v.begin(), v.end(),
945 []( const PCM_INSTALLATION_ENTRY& a, const PCM_INSTALLATION_ENTRY& b )
946 {
947 return ( a.install_timestamp < b.install_timestamp )
948 || ( a.install_timestamp == b.install_timestamp
949 && a.package.identifier < b.package.identifier );
950 } );
951
952 return v;
953}

References m_installed.

◆ GetInstalledPackageVersion()

const wxString & PLUGIN_CONTENT_MANAGER::GetInstalledPackageVersion ( const wxString &  aPackageId) const

Get the current version of an installed package.

Parameters
aPackageIdid of the package
Returns
current version
Exceptions
std::out_of_rangeif package with given id is not installed Returns current 3rd party directory path

Definition at line 957 of file pcm.cpp.

958{
959 wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
960 wxT( "Installed package not found." ) );
961
962 return m_installed.at( aPackageId ).current_version;
963}

References m_installed.

◆ GetPackageSearchRank()

int PLUGIN_CONTENT_MANAGER::GetPackageSearchRank ( const PCM_PACKAGE aPackage,
const wxString &  aSearchTerm 
)

Get the approximate measure of how much given package matches the search term.

Parameters
aPackagepackage metadata object
aSearchTermsearch term
Returns
int search rank, higher number means better match, 0 means no match

Definition at line 984 of file pcm.cpp.

986{
987 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS( " " ), wxTOKEN_STRTOK );
988 int rank = 0;
989
990 const auto find_term_matches =
991 [&]( const wxString& str )
992 {
993 int result = 0;
994 wxString lower = str.Lower();
995
996 for( const wxString& term : terms )
997 {
998 if( lower.Find( term ) != wxNOT_FOUND )
999 result += 1;
1000 }
1001
1002 return result;
1003 };
1004
1005 // Match on package id
1006 if( terms.size() == 1 && terms[0] == aPackage.identifier )
1007 rank += 10000;
1008
1009 if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
1010 rank += 1000;
1011
1012 // Match on package name
1013 rank += 500 * find_term_matches( aPackage.name );
1014
1015 // Match on tags
1016 for( const std::string& tag : aPackage.tags )
1017 rank += 100 * find_term_matches( wxString( tag ) );
1018
1019 // Match on package description
1020 rank += 10 * find_term_matches( aPackage.description );
1021 rank += 10 * find_term_matches( aPackage.description_full );
1022
1023 // Match on author/maintainer
1024 rank += find_term_matches( aPackage.author.name );
1025
1026 if( aPackage.maintainer )
1027 rank += 3 * find_term_matches( aPackage.maintainer->name );
1028
1029 // Match on resources
1030 for( const std::pair<const std::string, wxString>& entry : aPackage.resources )
1031 {
1032 rank += find_term_matches( entry.first );
1033 rank += find_term_matches( entry.second );
1034 }
1035
1036 // Match on license
1037 if( terms.size() == 1 && terms[0] == aPackage.license )
1038 rank += 1;
1039
1040 return rank;
1041}
wxString name
Definition: pcm_data.h:63
wxString description
Definition: pcm_data.h:97
wxString description_full
Definition: pcm_data.h:98
wxString license
Definition: pcm_data.h:103
std::vector< std::string > tags
Definition: pcm_data.h:105
STRING_MAP resources
Definition: pcm_data.h:104
std::optional< PCM_CONTACT > maintainer
Definition: pcm_data.h:102
PCM_CONTACT author
Definition: pcm_data.h:101

References PCM_PACKAGE::author, PCM_PACKAGE::description, PCM_PACKAGE::description_full, PCM_PACKAGE::identifier, PCM_PACKAGE::license, PCM_PACKAGE::maintainer, PCM_CONTACT::name, PCM_PACKAGE::name, PCM_PACKAGE::resources, and PCM_PACKAGE::tags.

◆ GetPackageState()

PCM_PACKAGE_STATE PLUGIN_CONTENT_MANAGER::GetPackageState ( const wxString &  aRepositoryId,
const wxString &  aPackageId 
)

Get current state of the package.

Parameters
aRepositoryIdrepository id
aPackageIdpackage id
Returns
PCM_PACKAGE_STATE

Definition at line 829 of file pcm.cpp.

831{
832 bool installed = m_installed.find( aPackageId ) != m_installed.end();
833
834 if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
835 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
836
837 const PCM_REPOSITORY* repo;
838
839 try
840 {
841 repo = &getCachedRepository( aRepositoryId );
842 }
843 catch( ... )
844 {
845 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
846 }
847
848 if( repo->package_map.count( aPackageId ) == 0 )
849 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
850
851 const PCM_PACKAGE& pkg = repo->package_list[repo->package_map.at( aPackageId )];
852
853 if( installed )
854 {
855 // Package is installed, check for available updates at the same or
856 // higher (numerically lower) version stability level
857 wxString update_version = GetPackageUpdateVersion( pkg );
858
859 return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
860 }
861 else
862 {
863 // Find any compatible version
864 auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
865 []( const PACKAGE_VERSION& ver )
866 {
867 return ver.compatible;
868 } );
869
870 return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
871 }
872}
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
Definition: pcm.cpp:405
const bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
Definition: pcm.cpp:414
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
Definition: pcm.cpp:875
@ PPS_INSTALLED
Definition: pcm.h:58
@ PPS_UNAVAILABLE
Definition: pcm.h:57
@ PPS_UPDATE_AVAILABLE
Definition: pcm.h:61
@ PPS_AVAILABLE
Definition: pcm.h:56
Repository reference to a resource.
Definition: pcm_data.h:95

References CacheRepository(), getCachedRepository(), GetPackageUpdateVersion(), m_installed, PCM_REPOSITORY::package_list, PCM_REPOSITORY::package_map, PPS_AVAILABLE, PPS_INSTALLED, PPS_UNAVAILABLE, PPS_UPDATE_AVAILABLE, and PCM_PACKAGE::versions.

◆ GetPackageUpdateVersion()

const wxString PLUGIN_CONTENT_MANAGER::GetPackageUpdateVersion ( const PCM_PACKAGE aPackage)

Get the preferred package update version or empty string if there is none.

Works only for installed packages and returns highest compatible version greater than currently installed that is at the same or higher (numerically lower) version stability level.

Parameters
aPackagepackage
Returns
package version string

Definition at line 875 of file pcm.cpp.

876{
877 wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
878 wxT( "GetPackageUpdateVersion called on a not installed package" ) );
879
880 const PCM_INSTALLATION_ENTRY& entry = m_installed.at( aPackage.identifier );
881
882 auto installed_ver_it = std::find_if(
883 entry.package.versions.begin(), entry.package.versions.end(),
884 [&]( const PACKAGE_VERSION& ver )
885 {
886 return ver.version == entry.current_version;
887 } );
888
889 wxASSERT_MSG( installed_ver_it != entry.package.versions.end(),
890 wxT( "Installed package version not found" ) );
891
892 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
893 [&]( const PACKAGE_VERSION& ver )
894 {
895 return ver.compatible
896 && installed_ver_it->status >= ver.status
897 && installed_ver_it->parsed_version < ver.parsed_version;
898 } );
899
900 return ver_it == aPackage.versions.end() ? wxString( wxT( "" ) ) : ver_it->version;
901}

References PCM_PACKAGE::identifier, m_installed, PCM_INSTALLATION_ENTRY::package, and PCM_PACKAGE::versions.

Referenced by GetPackageState().

◆ GetRepositoryList()

const STRING_TUPLE_LIST & PLUGIN_CONTENT_MANAGER::GetRepositoryList ( ) const
inline

Get list of repositories.

Returns
const STRING_TUPLE_LIST& list of repositories in <id, URL, name> tuple format

Definition at line 186 of file pcm.h.

186{ return m_repository_list; }

References m_repository_list.

◆ GetRepositoryPackageBitmaps()

std::unordered_map< wxString, wxBitmap > PLUGIN_CONTENT_MANAGER::GetRepositoryPackageBitmaps ( const wxString &  aRepositoryId)

Get the icon bitmaps for repository packages.

Repository package icons are taken from repository's resources zip file

Parameters
aRepositoryIdid of the repository
Returns
map of package id -> bitmap

Definition at line 1045 of file pcm.cpp.

1046{
1047 std::unordered_map<wxString, wxBitmap> bitmaps;
1048
1049 wxFileName resources_file = wxFileName( PATHS::GetUserCachePath(), wxT( "resources.zip" ) );
1050 resources_file.AppendDir( wxT( "pcm" ) );
1051 resources_file.AppendDir( aRepositoryId );
1052
1053 if( !resources_file.FileExists() )
1054 return bitmaps;
1055
1056 wxFFileInputStream stream( resources_file.GetFullPath() );
1057 wxZipInputStream zip( stream );
1058
1059 if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
1060 return bitmaps;
1061
1062 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
1063 {
1064 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1065 (wxChar) 0 );
1066
1067 if( path_parts.size() != 2 || path_parts[1] != wxT( "icon.png" ) )
1068 continue;
1069
1070 try
1071 {
1072 wxMemoryInputStream image_stream( zip, entry->GetSize() );
1073 wxImage image( image_stream, wxBITMAP_TYPE_PNG );
1074 bitmaps.emplace( path_parts[0], wxBitmap( image ) );
1075 }
1076 catch( ... )
1077 {
1078 // Log and ignore
1079 wxLogTrace( wxT( "Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1080 resources_file.GetFullPath() );
1081 }
1082 }
1083
1084 return bitmaps;
1085}

References PATHS::GetUserCachePath(), image, and zip.

◆ GetRepositoryPackages()

const std::vector< PCM_PACKAGE > & PLUGIN_CONTENT_MANAGER::GetRepositoryPackages ( const wxString &  aRepositoryId) const

Get the packages metadata from a previously cached repository.

This should only be called after a successful CacheRepository call

Parameters
aRepositoryIdid of the repository
Returns
list of package metadata objects

Definition at line 729 of file pcm.cpp.

730{
731 static std::vector<PCM_PACKAGE> empty{};
732
733 try
734 {
735 return getCachedRepository( aRepositoryId ).package_list;
736 }
737 catch( ... )
738 {
739 return empty;
740 }
741}
static bool empty(const wxTextEntryBase *aCtrl)

References empty(), getCachedRepository(), and PCM_REPOSITORY::package_list.

◆ IsPackagePinned()

bool PLUGIN_CONTENT_MANAGER::IsPackagePinned ( const wxString &  aPackageId) const

Returns pinned status of a package.

Parameters
aPackageIdpackage id
Returns
true if package is installed and is pinned
false if package is not installed or not pinned

Definition at line 966 of file pcm.cpp.

967{
968 if( m_installed.find( aPackageId ) == m_installed.end() )
969 return false;
970
971 return m_installed.at( aPackageId ).pinned;
972}

References m_installed.

◆ MarkInstalled()

void PLUGIN_CONTENT_MANAGER::MarkInstalled ( const PCM_PACKAGE aPackage,
const wxString &  aVersion,
const wxString &  aRepositoryId 
)

Mark package as installed.

Parameters
aPackagepackage metadata
aVersioninstalled package version
aRepositoryIdid of the source repository or empty estring if installed from a local file

Definition at line 787 of file pcm.cpp.

789{
790 // In case of package update remove old data but keep pinned state
791 bool pinned = false;
792
793 if( m_installed.count( aPackage.identifier ) )
794 {
795 pinned = m_installed.at( aPackage.identifier ).pinned;
796 MarkUninstalled( aPackage );
797 }
798
800 entry.package = aPackage;
801 entry.current_version = aVersion;
802 entry.repository_id = aRepositoryId;
803
804 try
805 {
806 if( !aRepositoryId.IsEmpty() )
807 entry.repository_name = getCachedRepository( aRepositoryId ).name;
808 else
809 entry.repository_name = _( "Local file" );
810 }
811 catch( ... )
812 {
813 entry.repository_name = _( "Unknown" );
814 }
815
817 entry.pinned = pinned;
818
819 m_installed.emplace( aPackage.identifier, entry );
820}
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition: pcm.cpp:823
wxString repository_id
Definition: pcm_data.h:141
bool pinned
Definition: pcm_data.h:144
wxString name
Definition: pcm_data.h:123

References _, PCM_INSTALLATION_ENTRY::current_version, getCachedRepository(), getCurrentTimestamp(), PCM_PACKAGE::identifier, PCM_INSTALLATION_ENTRY::install_timestamp, m_installed, MarkUninstalled(), PCM_REPOSITORY::name, PCM_INSTALLATION_ENTRY::package, PCM_INSTALLATION_ENTRY::pinned, PCM_INSTALLATION_ENTRY::repository_id, and PCM_INSTALLATION_ENTRY::repository_name.

◆ MarkUninstalled()

void PLUGIN_CONTENT_MANAGER::MarkUninstalled ( const PCM_PACKAGE aPackage)

Mark package as uninstalled.

Parameters
aPackagepackage metadata

Definition at line 823 of file pcm.cpp.

824{
825 m_installed.erase( aPackage.identifier );
826}

References PCM_PACKAGE::identifier, and m_installed.

Referenced by MarkInstalled().

◆ preparePackage()

void PLUGIN_CONTENT_MANAGER::preparePackage ( PCM_PACKAGE aPackage)
staticprivate

Parses version strings and calculates compatibility.

This should be called after loading package metadata from repository or from installation entries

Parameters
aPackagepackage metadata object Returns current UTC timestamp

Definition at line 649 of file pcm.cpp.

650{
651 // Parse package version strings
652 for( PACKAGE_VERSION& ver : aPackage.versions )
653 {
654 int epoch = 0, major = 0, minor = 0, patch = 0;
655
656 if( ver.version_epoch )
657 epoch = *ver.version_epoch;
658
659 wxStringTokenizer version_tokenizer( ver.version, wxT( "." ) );
660
661 major = wxAtoi( version_tokenizer.GetNextToken() );
662
663 if( version_tokenizer.HasMoreTokens() )
664 minor = wxAtoi( version_tokenizer.GetNextToken() );
665
666 if( version_tokenizer.HasMoreTokens() )
667 patch = wxAtoi( version_tokenizer.GetNextToken() );
668
669 ver.parsed_version = std::make_tuple( epoch, major, minor, patch );
670
671 // Determine compatibility
672 ver.compatible = true;
673
674 auto parse_version_tuple =
675 []( const wxString& version, int deflt )
676 {
677 int ver_major = deflt;
678 int ver_minor = deflt;
679 int ver_patch = deflt;
680
681 wxStringTokenizer tokenizer( version, wxT( "." ) );
682
683 ver_major = wxAtoi( tokenizer.GetNextToken() );
684
685 if( tokenizer.HasMoreTokens() )
686 ver_minor = wxAtoi( tokenizer.GetNextToken() );
687
688 if( tokenizer.HasMoreTokens() )
689 ver_patch = wxAtoi( tokenizer.GetNextToken() );
690
691 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
692 };
693
694 if( parse_version_tuple( ver.kicad_version, 0 ) > m_kicad_version )
695 ver.compatible = false;
696
697 if( ver.kicad_version_max
698 && parse_version_tuple( *ver.kicad_version_max, 999 ) < m_kicad_version )
699 ver.compatible = false;
700
701#ifdef __WXMSW__
702 wxString platform = wxT( "windows" );
703#endif
704#ifdef __WXOSX__
705 wxString platform = wxT( "macos" );
706#endif
707#ifdef __WXGTK__
708 wxString platform = wxT( "linux" );
709#endif
710
711 if( ver.platforms.size() > 0
712 && std::find( ver.platforms.begin(), ver.platforms.end(), platform )
713 == ver.platforms.end() )
714 {
715 ver.compatible = false;
716 }
717 }
718
719 // Sort by descending version
720 std::sort( aPackage.versions.begin(), aPackage.versions.end(),
721 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
722 {
723 return a.parsed_version > b.parsed_version;
724 } );
725}
static const std::tuple< int, int, int > m_kicad_version
Definition: pcm.h:401
bool compatible
Definition: pcm_data.h:89
std::optional< wxString > kicad_version_max
Definition: pcm_data.h:84
std::optional< int > version_epoch
Definition: pcm_data.h:76
std::vector< std::string > platforms
Definition: pcm_data.h:82
std::tuple< int, int, int, int > parsed_version
Definition: pcm_data.h:88

References PACKAGE_VERSION::compatible, PACKAGE_VERSION::kicad_version, PACKAGE_VERSION::kicad_version_max, m_kicad_version, PACKAGE_VERSION::parsed_version, PACKAGE_VERSION::platforms, PACKAGE_VERSION::version, PACKAGE_VERSION::version_epoch, and PCM_PACKAGE::versions.

Referenced by CacheRepository().

◆ ReadEnvVar()

void PLUGIN_CONTENT_MANAGER::ReadEnvVar ( )

Stores 3rdparty path from environment variables.

Definition at line 223 of file pcm.cpp.

224{
225 // Get 3rd party path
226 const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
227 auto it = env.find( wxT( "KICAD7_3RD_PARTY" ) );
228
229 if( it != env.end() && !it->second.GetValue().IsEmpty() )
230 m_3rdparty_path = it->second.GetValue();
231 else
233}
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition: paths.cpp:129
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:111

References PATHS::GetDefault3rdPartyPath(), m_3rdparty_path, and Pgm().

Referenced by PLUGIN_CONTENT_MANAGER().

◆ RunBackgroundUpdate()

void PLUGIN_CONTENT_MANAGER::RunBackgroundUpdate ( )

Runs a background update thread that checks for new package versions.

Definition at line 1131 of file pcm.cpp.

1132{
1133 // If the thread is already running don't create it again
1134 if( m_updateThread.joinable() )
1135 return;
1136
1137 m_statusReporter = std::make_shared<STATUS_TEXT_REPORTER>( m_statusCallback );
1138
1139 m_updateThread = std::thread(
1140 [this]()
1141 {
1142 if( m_installed.size() == 0 )
1143 return;
1144
1145 // Only fetch repositories that have installed not pinned packages
1146 std::unordered_set<wxString> repo_ids;
1147
1148 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1149 {
1150 if( !pair.second.pinned )
1151 repo_ids.insert( pair.second.repository_id );
1152 }
1153
1154 for( const auto& [ repository_id, name, url ] : m_repository_list )
1155 {
1156 if( repo_ids.count( repository_id ) == 0 )
1157 continue;
1158
1159 CacheRepository( repository_id );
1160
1161 if( m_statusReporter->IsCancelled() )
1162 break;
1163 }
1164
1165 if( m_statusReporter->IsCancelled() )
1166 return;
1167
1168 // Count packages with updates
1169 int availableUpdateCount = 0;
1170
1171 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1172 {
1173 PCM_INSTALLATION_ENTRY& entry = pair.second;
1174
1175 if( m_repository_cache.find( entry.repository_id ) != m_repository_cache.end() )
1176 {
1177 PCM_PACKAGE_STATE state = GetPackageState( entry.repository_id,
1178 entry.package.identifier );
1179
1180 if( state == PPS_UPDATE_AVAILABLE && !entry.pinned )
1181 availableUpdateCount++;
1182 }
1183
1184 if( m_statusReporter->IsCancelled() )
1185 return;
1186 }
1187
1188 // Update the badge on PCM button
1189 m_availableUpdateCallback( availableUpdateCount );
1190
1191 m_statusCallback( availableUpdateCount > 0 ? _( "Package updates are available" )
1192 : _( "No package updates available" ) );
1193 } );
1194}
const char * name
Definition: DXF_plotter.cpp:56
std::thread m_updateThread
Definition: pcm.h:404

References _, m_availableUpdateCallback, m_installed, m_repository_list, m_statusCallback, m_statusReporter, m_updateThread, and name.

◆ SaveInstalledPackages()

void PLUGIN_CONTENT_MANAGER::SaveInstalledPackages ( )

Saves metadata of installed packages to disk.

Path is <user settings>/installed_packages.json

Definition at line 910 of file pcm.cpp.

911{
912 try
913 {
915 js["packages"] = nlohmann::json::array();
916
917 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
918 {
919 js["packages"].emplace_back( pair.second );
920 }
921
922 wxFileName f( SETTINGS_MANAGER::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
923 std::ofstream stream( f.GetFullPath().ToUTF8() );
924
925 stream << std::setw( 4 ) << js << std::endl;
926 }
927 catch( nlohmann::detail::exception& )
928 {
929 // Ignore
930 }
931}

References SETTINGS_MANAGER::GetUserSettingsPath(), and m_installed.

◆ SetDialogWindow()

void PLUGIN_CONTENT_MANAGER::SetDialogWindow ( wxWindow *  aDialog)
inline

Set the Dialog Window.

PCM can effectively run in "silent" mode with a background thread that reports to kicad manager window status bar. Setting valid window pointer here will switch it to GUI mode with WX_PROGRESS_DIALOG popup for downloads.

Parameters
aDialogparent dialog for progress window

Definition at line 327 of file pcm.h.

327{ m_dialog = aDialog; };

References m_dialog.

◆ SetPinned()

void PLUGIN_CONTENT_MANAGER::SetPinned ( const wxString &  aPackageId,
const bool  aPinned 
)

Set the pinned status of a package.

no-op for not installed packages

Parameters
aPackageIdpackage id
aPinnedpinned status

Definition at line 975 of file pcm.cpp.

976{
977 if( m_installed.find( aPackageId ) == m_installed.end() )
978 return;
979
980 m_installed.at( aPackageId ).pinned = aPinned;
981}

References m_installed.

◆ SetRepositoryList()

void PLUGIN_CONTENT_MANAGER::SetRepositoryList ( const STRING_PAIR_LIST aRepositories)

Set list of repositories.

Discards cache of repositories that were previously cached but are not on the new list of repositories.

Parameters
aRepositorieslist of <URL, name> pairs of repositories

Definition at line 744 of file pcm.cpp.

745{
746 // Clean up cache folder if repository is not in new list
747 for( const std::tuple<wxString, wxString, wxString>& entry : m_repository_list )
748 {
749 auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
750 [&]( const auto& new_entry )
751 {
752 return new_entry.first == std::get<1>( entry );
753 } );
754
755 if( it == aRepositories.end() )
756 {
757 DiscardRepositoryCache( std::get<0>( entry ) );
758 }
759 }
760
761 m_repository_list.clear();
762 m_repository_cache.clear();
763
764 for( const std::pair<wxString, wxString>& repo : aRepositories )
765 {
766 std::string url_sha = picosha2::hash256_hex_string( repo.second );
767 m_repository_list.push_back( std::make_tuple( url_sha.substr( 0, 16 ), repo.first,
768 repo.second ) );
769 }
770}
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
Definition: pcm.cpp:773

References DiscardRepositoryCache(), m_repository_cache, and m_repository_list.

◆ StopBackgroundUpdate()

void PLUGIN_CONTENT_MANAGER::StopBackgroundUpdate ( )

Interrupts and joins() the update thread.

Definition at line 1197 of file pcm.cpp.

1198{
1199 if( m_updateThread.joinable() )
1200 {
1201 m_statusReporter->Cancel();
1202 m_updateThread.join();
1203 }
1204}

References m_statusReporter, and m_updateThread.

Referenced by ~PLUGIN_CONTENT_MANAGER().

◆ updateInstalledPackagesMetadata()

void PLUGIN_CONTENT_MANAGER::updateInstalledPackagesMetadata ( const wxString &  aRepositoryId)
private

Updates metadata of installed packages from freshly fetched repo.

This completely replaces all fields including description. Only exception is versions field, if currently installed version is missing from the repo metadata it is manually added back in to correctly display in the installed packages.

Parameters
aRepositoryId

Definition at line 585 of file pcm.cpp.

586{
587 const PCM_REPOSITORY* repository;
588
589 try
590 {
591 repository = &getCachedRepository( aRepositoryId );
592 }
593 catch( ... )
594 {
595 wxLogDebug( "Invalid/Missing repository " + aRepositoryId );
596 return;
597 }
598
599 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
600 {
601 PCM_INSTALLATION_ENTRY& entry = pair.second;
602
603 // If current package is not from this repository, skip it
604 if( entry.repository_id != aRepositoryId )
605 continue;
606
607 // If current package is no longer in this repository, keep it as is
608 if( repository->package_map.count( entry.package.identifier ) == 0 )
609 continue;
610
611 std::optional<PACKAGE_VERSION> current_version;
612
613 auto current_version_it =
614 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
615 [&]( const PACKAGE_VERSION& version )
616 {
617 return version.version == entry.current_version;
618 } );
619
620 if( current_version_it != entry.package.versions.end() )
621 current_version = *current_version_it; // copy
622
623 // Copy repository metadata into installation entry
624 entry.package = repository->package_list[repository->package_map.at( entry.package.identifier )];
625
626 // Insert current version if it's missing from repository metadata
627 current_version_it =
628 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
629 [&]( const PACKAGE_VERSION& version )
630 {
631 return version.version == entry.current_version;
632 } );
633
634 if( current_version_it == entry.package.versions.end() )
635 {
636 entry.package.versions.emplace_back( *current_version );
637
638 // Re-sort the versions by descending version
639 std::sort( entry.package.versions.begin(), entry.package.versions.end(),
640 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
641 {
642 return a.parsed_version > b.parsed_version;
643 } );
644 }
645 }
646}

References getCachedRepository(), PCM_PACKAGE::identifier, m_installed, PCM_INSTALLATION_ENTRY::package, PCM_REPOSITORY::package_list, PCM_REPOSITORY::package_map, PCM_INSTALLATION_ENTRY::repository_id, and PCM_PACKAGE::versions.

Referenced by CacheRepository().

◆ ValidateJson()

void PLUGIN_CONTENT_MANAGER::ValidateJson ( const nlohmann::json aJson,
const nlohmann::json_uri &  aUri = nlohmann::json_uri( "#" ) 
) const

Validates json against a specific definition in the PCM schema.

Parameters
aJsonJSON object to validate
aUriJSON URI of a definition to validate against, default is root
Exceptions
std::invalid_argumenton validation failure

Definition at line 335 of file pcm.cpp.

337{
338 THROWING_ERROR_HANDLER error_handler;
339 m_schema_validator.validate( aJson, error_handler, aUri );
340}

References m_schema_validator.

Referenced by fetchPackages(), and FetchRepository().

◆ VerifyHash()

bool PLUGIN_CONTENT_MANAGER::VerifyHash ( std::istream &  aStream,
const wxString &  aHash 
) const

Verifies SHA256 hash of a binary stream.

Parameters
aStreaminput stream
aHashsha256 lowercase hex string
Returns
true if hash matches

Definition at line 392 of file pcm.cpp.

393{
394 std::vector<unsigned char> bytes( picosha2::k_digest_size );
395
396 picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
397 bytes.begin(), bytes.end() );
398 std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
399
400 return aHash.compare( hex_str ) == 0;
401}

Referenced by CacheRepository(), and fetchPackages().

Member Data Documentation

◆ DEFAULT_DOWNLOAD_MEM_LIMIT

constexpr size_t PLUGIN_CONTENT_MANAGER::DEFAULT_DOWNLOAD_MEM_LIMIT = 10 * 1024 * 1024
staticconstexprprivate

< Default download limit of 10 Mb to not use too much memory

Definition at line 346 of file pcm.h.

◆ m_3rdparty_path

wxString PLUGIN_CONTENT_MANAGER::m_3rdparty_path
private

◆ m_availableUpdateCallback

std::function<void( int )> PLUGIN_CONTENT_MANAGER::m_availableUpdateCallback
private

Definition at line 402 of file pcm.h.

Referenced by RunBackgroundUpdate().

◆ m_cache_path

wxString PLUGIN_CONTENT_MANAGER::m_cache_path
private

Definition at line 396 of file pcm.h.

◆ m_dialog

wxWindow* PLUGIN_CONTENT_MANAGER::m_dialog
private

◆ m_installed

◆ m_kicad_version

const std::tuple< int, int, int > PLUGIN_CONTENT_MANAGER::m_kicad_version
staticprivate
Initial value:
=
const std::tuple< int, int, int > & GetMajorMinorPatchTuple()
Get the build version numbers as a tuple.

Definition at line 401 of file pcm.h.

Referenced by preparePackage().

◆ m_repository_cache

std::unordered_map<wxString, PCM_REPOSITORY> PLUGIN_CONTENT_MANAGER::m_repository_cache
private

◆ m_repository_list

STRING_TUPLE_LIST PLUGIN_CONTENT_MANAGER::m_repository_list
private

Definition at line 398 of file pcm.h.

Referenced by CacheRepository(), GetRepositoryList(), RunBackgroundUpdate(), and SetRepositoryList().

◆ m_schema_validator

nlohmann::json_schema::json_validator PLUGIN_CONTENT_MANAGER::m_schema_validator
private

Definition at line 394 of file pcm.h.

Referenced by PLUGIN_CONTENT_MANAGER(), and ValidateJson().

◆ m_statusCallback

std::function<void( const wxString )> PLUGIN_CONTENT_MANAGER::m_statusCallback
private

Definition at line 403 of file pcm.h.

Referenced by RunBackgroundUpdate().

◆ m_statusReporter

std::shared_ptr<STATUS_TEXT_REPORTER> PLUGIN_CONTENT_MANAGER::m_statusReporter
private

Definition at line 406 of file pcm.h.

Referenced by CacheRepository(), RunBackgroundUpdate(), and StopBackgroundUpdate().

◆ m_updateThread

std::thread PLUGIN_CONTENT_MANAGER::m_updateThread
private

Definition at line 404 of file pcm.h.

Referenced by RunBackgroundUpdate(), and StopBackgroundUpdate().


The documentation for this class was generated from the following files: