KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcm.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2021 Andrew Lutsenko, anlutsenko at gmail dot com
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21// kicad_curl.h *must be* included before any wxWidgets header to avoid conflicts
22// at least on Windows/msys2
25
26#include "core/wx_stl_compat.h"
27#include <env_vars.h>
30#include "build_version.h"
31#include "paths.h"
32#include "pcm.h"
33#include <eda_base_frame.h>
34#include "dialogs/dialog_pcm.h"
35#include "pgm_base.h"
36#include "picosha2.h"
38#include <wx_filename.h>
40#include <kiway.h>
41
42#include <fstream>
43#include <iomanip>
44#include <memory>
45#include <wx/dir.h>
46#include <wx/filefn.h>
47#include <wx/image.h>
48#include <wx/mstream.h>
49#include <wx/tokenzr.h>
50#include <wx/wfstream.h>
51#include <wx/zipstrm.h>
52
53
59static const wxChar tracePcm[] = wxT( "KICAD_PCM" );
60
61static const std::string PCM_ACCEPT_V2 = "application/vnd.kicad.pcm.v2+json";
62
63
64const std::tuple<int, int, int> PLUGIN_CONTENT_MANAGER::m_kicad_version =
66
67
68class THROWING_ERROR_HANDLER : public nlohmann::json_schema::error_handler
69{
70 void error( const json::json_pointer& ptr, const json& instance,
71 const std::string& message ) override
72 {
73 throw std::invalid_argument( std::string( "At " ) + ptr.to_string() + ", value:\n"
74 + instance.dump() + "\n" + message + "\n" );
75 }
76};
77
78#include <locale_io.h>
79PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( std::function<void( int )> aAvailableUpdateCallback ) :
80 m_dialog( nullptr ),
81 m_availableUpdateCallback( aAvailableUpdateCallback ),
83{
84 ReadEnvVar();
85
86 // Read and store pcm schemas
87 wxFileName schema_v1_file( PATHS::GetStockDataPath( true ), wxS( "pcm.v1.schema.json" ) );
88 schema_v1_file.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
89 schema_v1_file.AppendDir( wxS( "schemas" ) );
90
91 m_schema_v1_validator = std::make_unique<JSON_SCHEMA_VALIDATOR>( schema_v1_file );
92
93 wxFileName schema_v2_file( PATHS::GetStockDataPath( true ), wxS( "pcm.v2.schema.json" ) );
94 schema_v2_file.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
95 schema_v2_file.AppendDir( wxS( "schemas" ) );
96
97 m_schema_v2_validator = std::make_unique<JSON_SCHEMA_VALIDATOR>( schema_v2_file );
98
99 // Load currently installed packages
100 wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
101
102 if( f.FileExists() )
103 {
104 std::ifstream installed_stream( f.GetFullPath().fn_str() );
105 nlohmann::json installed;
106
107 try
108 {
109 installed_stream >> installed;
110
111 if( installed.contains( "packages" ) && installed["packages"].is_array() )
112 {
113 for( const auto& js_entry : installed["packages"] )
114 {
115 PCM_INSTALLATION_ENTRY entry = js_entry.get<PCM_INSTALLATION_ENTRY>();
116 m_installed.emplace( entry.package.identifier, entry );
117 }
118 }
119 }
120 catch( std::exception& e )
121 {
122 wxLogError( wxString::Format( _( "Error loading installed packages list: %s" ),
123 e.what() ) );
124 }
125 }
126
127 // As a fall back populate installed from names of directories
128
129 for( const wxString& dir : PCM_PACKAGE_DIRECTORIES )
130 {
131 wxFileName d( m_3rdparty_path, wxEmptyString );
132 d.AppendDir( dir );
133
134 if( d.DirExists() )
135 {
136 wxDir package_dir( d.GetPath() );
137
138 if( !package_dir.IsOpened() )
139 continue;
140
141 wxString subdir;
142 bool more = package_dir.GetFirst( &subdir, "", wxDIR_DIRS | wxDIR_HIDDEN );
143
144 while( more )
145 {
146 wxString actual_package_id = subdir;
147 actual_package_id.Replace( '_', '.' );
148
149 if( m_installed.find( actual_package_id ) == m_installed.end() )
150 {
152 wxFileName subdir_file( d.GetPath(), subdir );
153
154 // wxFileModificationTime bugs out on windows for directories
155 wxStructStat stat;
156 int stat_code = wxStat( subdir_file.GetFullPath(), &stat );
157
158 entry.package.name = subdir;
159 entry.package.identifier = actual_package_id;
160 entry.current_version = "0.0";
161 entry.repository_name = wxT( "<unknown>" );
162
163 if( stat_code == 0 )
164 entry.install_timestamp = stat.st_mtime;
165
166 PACKAGE_VERSION version;
167 version.version = "0.0";
168 version.status = PVS_STABLE;
170
171 entry.package.versions.emplace_back( version );
172
173 m_installed.emplace( actual_package_id, entry );
174 }
175
176 more = package_dir.GetNext( &subdir );
177 }
178 }
179 }
180
181 // Calculate package compatibility
182 std::for_each( m_installed.begin(), m_installed.end(),
183 [&]( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
184 {
185 PreparePackage( entry.second.package );
186 } );
187}
188
189
191{
192 // Get 3rd party path
193 const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
194
195 if( std::optional<wxString> v = ENV_VAR::GetVersionedEnvVarValue( env, wxT( "3RD_PARTY" ) ) )
196 m_3rdparty_path = *v;
197 else
199}
200
201
202bool PLUGIN_CONTENT_MANAGER::DownloadToStream( const wxString& aUrl, std::ostream* aOutput,
203 PROGRESS_REPORTER* aReporter,
204 const size_t aSizeLimit,
205 const std::string& aAccept )
206{
207 bool size_exceeded = false;
208
209 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
210 {
211 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
212 {
213 size_exceeded = true;
214
215 // Non zero return means abort.
216 return true;
217 }
218
219 if( dltotal > 1000 )
220 {
221 aReporter->SetCurrentProgress( dlnow / (double) dltotal );
222 aReporter->Report( wxString::Format( _( "Downloading %lld/%lld kB" ), dlnow / 1000,
223 dltotal / 1000 ) );
224 }
225 else
226 {
227 aReporter->SetCurrentProgress( 0.0 );
228 }
229
230 return !aReporter->KeepRefreshing();
231 };
232
233 KICAD_CURL_EASY curl;
234 curl.SetOutputStream( aOutput );
235 curl.SetURL( aUrl.ToUTF8().data() );
236 curl.SetFollowRedirects( true );
237 curl.SetTransferCallback( callback, 250000L );
238
239 // Bound stalled transfers without capping total time. DownloadToStream is shared
240 // by metadata fetches and large package/resource downloads, so a fixed total
241 // timeout would abort legitimate long downloads on slow links. Abort only if the
242 // transfer rate stays below 100 B/s for 30 seconds.
243 curl.SetStallTimeout( 100L, 30L );
244
245 if( !aAccept.empty() )
246 curl.SetHeader( "Accept", aAccept );
247
248 int code = curl.Perform();
249
250 if( !aReporter->IsCancelled() )
251 aReporter->SetCurrentProgress( 1.0 );
252
253 if( code != CURLE_OK )
254 {
255 if( m_dialog )
256 {
257 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
258 wxMessageBox( _( "Download is too large." ) );
259 else if( code != CURLE_ABORTED_BY_CALLBACK )
260 wxLogError( wxString( curl.GetErrorText( code ) ) );
261 }
262
263 return false;
264 }
265
266 return true;
267}
268
269
270bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository,
271 PROGRESS_REPORTER* aReporter )
272{
273 std::stringstream repository_stream;
274
275 aReporter->SetTitle( _( "Fetching repository" ) );
276
277 if( !DownloadToStream( aUrl, &repository_stream, aReporter, 20480,
278 PCM_ACCEPT_V2 ) )
279 {
280 return false;
281 }
282
283 nlohmann::json repository_json;
284
285 try
286 {
287 repository_stream >> repository_json;
288
289 int schema_version = 1;
290
291 if( repository_json.contains( "schema_version" ) )
292 schema_version = repository_json["schema_version"].get<int>();
293
294 if( schema_version >= 2 )
295 {
296 ValidateJson( repository_json, *m_schema_v2_validator,
297 nlohmann::json_uri( "#/definitions/Repository" ) );
298 }
299 else
300 {
301 ValidateJson( repository_json, *m_schema_v1_validator,
302 nlohmann::json_uri( "#/definitions/Repository" ) );
303 }
304
305 aRepository = repository_json.get<PCM_REPOSITORY>();
306 aRepository.schema_version = schema_version;
307 }
308 catch( const std::exception& e )
309 {
310 if( m_dialog )
311 {
312 wxLogError( _( "Unable to parse repository: %s" ), e.what() );
313 wxLogError( _( "The given repository URL does not look like a valid KiCad package "
314 "repository. Please double check the URL." ) );
315 }
316
317 return false;
318 }
319
320 return true;
321}
322
323
324void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json& aJson,
325 const nlohmann::json_uri& aUri ) const
326{
327 THROWING_ERROR_HANDLER error_handler;
328 m_schema_v2_validator->Validate( aJson, error_handler, aUri );
329}
330
331
332void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json& aJson,
333 const JSON_SCHEMA_VALIDATOR& aValidator,
334 const nlohmann::json_uri& aUri ) const
335{
336 THROWING_ERROR_HANDLER error_handler;
337 aValidator.Validate( aJson, error_handler, aUri );
338}
339
340
341bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString& aUrl,
342 const std::optional<wxString>& aHash,
343 std::vector<PCM_PACKAGE>& aPackages,
344 PROGRESS_REPORTER* aReporter,
345 int aSchemaVersion )
346{
347 std::stringstream packages_stream;
348
349 aReporter->SetTitle( _( "Fetching repository packages" ) );
350
351 if( !DownloadToStream( aUrl, &packages_stream, aReporter, DEFAULT_DOWNLOAD_MEM_LIMIT,
352 PCM_ACCEPT_V2 ) )
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
374 const JSON_SCHEMA_VALIDATOR& validator =
375 ( aSchemaVersion >= 2 ) ? *m_schema_v2_validator : *m_schema_v1_validator;
376
377 ValidateJson( packages_json, validator,
378 nlohmann::json_uri( "#/definitions/PackageArray" ) );
379
380 aPackages = packages_json["packages"].get<std::vector<PCM_PACKAGE>>();
381 }
382 catch( std::exception& e )
383 {
384 if( m_dialog )
385 {
386 wxLogError( wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ),
387 e.what() ) );
388 }
389
390 return false;
391 }
392
393 return true;
394}
395
396
397bool PLUGIN_CONTENT_MANAGER::VerifyHash( std::istream& aStream, const wxString& aHash ) const
398{
399 std::vector<unsigned char> bytes( picosha2::k_digest_size );
400
401 picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
402 bytes.begin(), bytes.end() );
403 std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
404
405 return aHash.compare( hex_str ) == 0;
406}
407
408
409const PCM_REPOSITORY&
410PLUGIN_CONTENT_MANAGER::getCachedRepository( const wxString& aRepositoryId ) const
411{
412 wxASSERT_MSG( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end(),
413 wxT( "Repository is not cached." ) );
414
415 return m_repository_cache.at( aRepositoryId );
416}
417
418
419bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryId )
420{
421 if( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end() )
422 return true;
423
424 const auto repository_tuple =
425 std::find_if( m_repository_list.begin(), m_repository_list.end(),
426 [&aRepositoryId]( const std::tuple<wxString, wxString, wxString>& t )
427 {
428 return std::get<0>( t ) == aRepositoryId;
429 } );
430
431 if( repository_tuple == m_repository_list.end() )
432 return false;
433
434 wxString url = std::get<2>( *repository_tuple );
435
436 nlohmann::json js;
437 PCM_REPOSITORY current_repo;
438 PCM_REPOSITORY& current_repo_ref = current_repo;
439
440 std::shared_ptr<PROGRESS_REPORTER> reporter;
441
442 if( m_dialog )
443 reporter = std::make_shared<WX_PROGRESS_REPORTER>( m_dialog, wxT( "" ), 1, PR_CAN_ABORT );
444 else
445 reporter = m_updateBackgroundJob->m_reporter;
446
447 if( !FetchRepository( url, current_repo, reporter.get() ) )
448 return false;
449
450 bool packages_cache_exists = false;
451
452 // First load repository data from local filesystem if available.
453 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), wxT( "repository.json" ) );
454 repo_cache.AppendDir( wxT( "pcm" ) );
455 repo_cache.AppendDir( aRepositoryId );
456 wxFileName packages_cache( repo_cache.GetPath(), wxT( "packages.json" ) );
457
458 if( repo_cache.FileExists() && packages_cache.FileExists() )
459 {
460 std::ifstream repo_stream( repo_cache.GetFullPath().fn_str() );
461 PCM_REPOSITORY saved_repo;
462 try
463 {
464 repo_stream >> js;
465 saved_repo = js.get<PCM_REPOSITORY>();
466 }
467 catch( ... )
468 {
469 if( m_dialog )
470 wxLogError( _( "Failed to parse locally stored repository.json." ) );
471 }
472
473 if( saved_repo.packages.update_timestamp == current_repo.packages.update_timestamp )
474 {
475 // Cached repo is up to date, use data on disk
476 js.clear();
477 std::ifstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
478
479 try
480 {
481 packages_cache_stream >> js;
482 saved_repo.package_list = js["packages"].get<std::vector<PCM_PACKAGE>>();
483
484 for( size_t i = 0; i < saved_repo.package_list.size(); i++ )
485 {
486 PreparePackage( saved_repo.package_list[i] );
487 saved_repo.package_map[saved_repo.package_list[i].identifier] = i;
488 }
489
490 m_repository_cache[aRepositoryId] = std::move( saved_repo );
491
492 packages_cache_exists = true;
493 }
494 catch( ... )
495 {
496 if( m_dialog )
497 {
498 wxLogError( _( "Packages cache for current repository is corrupted, it will "
499 "be redownloaded." ) );
500 }
501 }
502 }
503 }
504
505 if( !packages_cache_exists )
506 {
507 // Cache doesn't exist or is out of date
508 if( !fetchPackages( current_repo.packages.url, current_repo.packages.sha256,
509 current_repo.package_list, reporter.get(),
510 current_repo.schema_version ) )
511 {
512 return false;
513 }
514
515 for( size_t i = 0; i < current_repo.package_list.size(); i++ )
516 {
517 PreparePackage( current_repo.package_list[i] );
518 current_repo.package_map[current_repo.package_list[i].identifier] = i;
519 }
520
521 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
522
523 std::ofstream repo_cache_stream( repo_cache.GetFullPath().fn_str() );
524 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
525
526 std::ofstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
527 js.clear();
528 js["packages"] = nlohmann::json( current_repo.package_list );
529 packages_cache_stream << std::setw( 4 ) << js << std::endl;
530
531 m_repository_cache[aRepositoryId] = std::move( current_repo );
532 current_repo_ref = m_repository_cache[aRepositoryId];
533 }
534
535 if( current_repo_ref.resources )
536 {
537 // Check resources file date, redownload if needed
538 PCM_RESOURCE_REFERENCE& resources = *current_repo_ref.resources;
539
540 wxFileName resource_file( repo_cache.GetPath(), wxT( "resources.zip" ) );
541
542 time_t mtime = 0;
543
544 if( resource_file.FileExists() )
545 mtime = wxFileModificationTime( resource_file.GetFullPath() );
546
547 if( mtime + 600 < getCurrentTimestamp() && mtime < (time_t) resources.update_timestamp )
548 {
549 std::ofstream resources_stream( resource_file.GetFullPath().fn_str(),
550 std::ios_base::binary );
551
552 reporter->SetTitle( _( "Downloading resources" ) );
553
554 // 100 Mb resource file limit
555 bool success = DownloadToStream( resources.url, &resources_stream, reporter.get(),
556 100 * 1024 * 1024 );
557
558 resources_stream.close();
559
560 if( success )
561 {
562 std::ifstream read_stream( resource_file.GetFullPath().fn_str(),
563 std::ios_base::binary );
564
565
566 if( resources.sha256 && !VerifyHash( read_stream, *resources.sha256 ) )
567 {
568 read_stream.close();
569
570 if( m_dialog )
571 {
572 wxLogError( _( "Resources file hash doesn't match and will not be used. "
573 "Repository may be corrupted." ) );
574 }
575
576 wxRemoveFile( resource_file.GetFullPath() );
577 }
578 }
579 else
580 {
581 // Not critical, just clean up the file
582 wxRemoveFile( resource_file.GetFullPath() );
583 }
584 }
585 }
586
587 updateInstalledPackagesMetadata( aRepositoryId );
588
589 return true;
590}
591
592
594{
595 const PCM_REPOSITORY* repository;
596
597 try
598 {
599 repository = &getCachedRepository( aRepositoryId );
600 }
601 catch( ... )
602 {
603 wxLogTrace( tracePcm, wxS( "Invalid/Missing repository " ) + aRepositoryId );
604 return;
605 }
606
607 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
608 {
609 PCM_INSTALLATION_ENTRY& entry = pair.second;
610
611 // If current package is not from this repository, skip it
612 if( entry.repository_id != aRepositoryId )
613 continue;
614
615 // If current package is no longer in this repository, keep it as is
616 if( repository->package_map.count( entry.package.identifier ) == 0 )
617 continue;
618
619 std::optional<PACKAGE_VERSION> current_version;
620
621 auto current_version_it =
622 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
623 [&]( const PACKAGE_VERSION& version )
624 {
625 return version.version == entry.current_version;
626 } );
627
628 if( current_version_it != entry.package.versions.end() )
629 current_version = *current_version_it; // copy
630
631 // Copy repository metadata into installation entry
632 entry.package = repository->package_list[repository->package_map.at( entry.package.identifier )];
633
634 // Insert current version if it's missing from repository metadata
635 current_version_it =
636 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
637 [&]( const PACKAGE_VERSION& version )
638 {
639 return version.version == entry.current_version;
640 } );
641
642 if( current_version_it == entry.package.versions.end() )
643 {
644 entry.package.versions.emplace_back( *current_version );
645
646 // Re-sort the versions by descending version
647 std::sort( entry.package.versions.begin(), entry.package.versions.end(),
648 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
649 {
650 return a.parsed_version > b.parsed_version;
651 } );
652 }
653 }
654}
655
656
658{
659 // Parse package version strings
660 for( PACKAGE_VERSION& ver : aPackage.versions )
661 {
662 int epoch = 0, major = 0, minor = 0, patch = 0;
663
664 if( ver.version_epoch )
665 epoch = *ver.version_epoch;
666
667 wxStringTokenizer version_tokenizer( ver.version, "." );
668
669 major = wxAtoi( version_tokenizer.GetNextToken() );
670
671 if( version_tokenizer.HasMoreTokens() )
672 minor = wxAtoi( version_tokenizer.GetNextToken() );
673
674 if( version_tokenizer.HasMoreTokens() )
675 patch = wxAtoi( version_tokenizer.GetNextToken() );
676
677 ver.parsed_version = std::make_tuple( epoch, major, minor, patch );
678
679 // Determine compatibility
680 ver.compatible = true;
681
682 auto parse_version_tuple =
683 []( const wxString& version, int deflt )
684 {
685 int ver_major = deflt;
686 int ver_minor = deflt;
687 int ver_patch = deflt;
688
689 wxStringTokenizer tokenizer( version, "." );
690
691 ver_major = wxAtoi( tokenizer.GetNextToken() );
692
693 if( tokenizer.HasMoreTokens() )
694 ver_minor = wxAtoi( tokenizer.GetNextToken() );
695
696 if( tokenizer.HasMoreTokens() )
697 ver_patch = wxAtoi( tokenizer.GetNextToken() );
698
699 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
700 };
701
702 if( parse_version_tuple( ver.kicad_version, 0 ) > m_kicad_version )
703 ver.compatible = false;
704
705 if( ver.kicad_version_max
706 && parse_version_tuple( *ver.kicad_version_max, 999 ) < m_kicad_version )
707 ver.compatible = false;
708
709#if defined( _WIN32 )
710 wxString platform = wxT( "windows" );
711#elif defined( __APPLE__ )
712 wxString platform = wxT( "macos" );
713#else
714 wxString platform = wxT( "linux" );
715#endif
716
717 if( ver.platforms.size() > 0
718 && std::find( ver.platforms.begin(), ver.platforms.end(), platform )
719 == ver.platforms.end() )
720 {
721 ver.compatible = false;
722 }
723 else if( UsesSWIGRuntime( aPackage, ver.version ) )
724 {
725 ver.compatible = false;
726 }
727 }
728
729 // Sort by descending version
730 std::sort( aPackage.versions.begin(), aPackage.versions.end(),
731 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
732 {
733 return a.parsed_version > b.parsed_version;
734 } );
735}
736
737
738bool PLUGIN_CONTENT_MANAGER::UsesSWIGRuntime( const PCM_PACKAGE& aPackage, const wxString& aVersion )
739{
740 if( !( aPackage.type == PT_PLUGIN || aPackage.type == PT_FAB ) )
741 return false;
742
743 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
744 [&]( const PACKAGE_VERSION& ver )
745 {
746 return ver.version == aVersion;
747 } );
748
749 if( ver_it == aPackage.versions.end() )
750 return false;
751
752 return ver_it->runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_SWIG;
753}
754
755
756const std::vector<PCM_PACKAGE>&
757PLUGIN_CONTENT_MANAGER::GetRepositoryPackages( const wxString& aRepositoryId ) const
758{
759 static std::vector<PCM_PACKAGE> empty{};
760
761 try
762 {
763 return getCachedRepository( aRepositoryId ).package_list;
764 }
765 catch( ... )
766 {
767 return empty;
768 }
769}
770
771
773{
774 // Clean up cache folder if repository is not in new list
775 for( const std::tuple<wxString, wxString, wxString>& entry : m_repository_list )
776 {
777 auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
778 [&]( const auto& new_entry )
779 {
780 return new_entry.first == std::get<1>( entry );
781 } );
782
783 if( it == aRepositories.end() )
784 {
785 DiscardRepositoryCache( std::get<0>( entry ) );
786 }
787 }
788
789 m_repository_list.clear();
790 m_repository_cache.clear();
791
792 for( const std::pair<wxString, wxString>& repo : aRepositories )
793 {
794 std::string url_sha = picosha2::hash256_hex_string( repo.second );
795 m_repository_list.push_back( std::make_tuple( url_sha.substr( 0, 16 ), repo.first,
796 repo.second ) );
797 }
798}
799
800
801void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepositoryId )
802{
803 if( m_repository_cache.count( aRepositoryId ) > 0 )
804 m_repository_cache.erase( aRepositoryId );
805
806 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), "" );
807 repo_cache.AppendDir( wxT( "pcm" ) );
808 repo_cache.AppendDir( aRepositoryId );
809
810 if( repo_cache.DirExists() )
811 repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
812}
813
814
815void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
816 const wxString& aRepositoryId )
817{
818 // In case of package update remove old data but keep pinned state
819 bool pinned = false;
820
821 if( m_installed.count( aPackage.identifier ) )
822 {
823 pinned = m_installed.at( aPackage.identifier ).pinned;
824 MarkUninstalled( aPackage );
825 }
826
828 entry.package = aPackage;
829 entry.current_version = aVersion;
830 entry.repository_id = aRepositoryId;
831
832 try
833 {
834 if( !aRepositoryId.IsEmpty() )
835 entry.repository_name = getCachedRepository( aRepositoryId ).name;
836 else
837 entry.repository_name = _( "Local file" );
838 }
839 catch( ... )
840 {
841 entry.repository_name = _( "Unknown" );
842 }
843
845 entry.pinned = pinned;
846
847 m_installed.emplace( aPackage.identifier, entry );
848
849 if( m_dialog
850 && ( aPackage.versions[0].runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_IPC )
851 && !Pgm().GetCommonSettings()->m_Api.enable_server )
852 {
853 // Defer the prompt until after installation completes
854 // to avoid UI operations during wxSafeYield
856 }
857}
858
859
861{
862 m_installed.erase( aPackage.identifier );
863}
864
865
867 const wxString& aPackageId )
868{
869 bool installed = m_installed.find( aPackageId ) != m_installed.end();
870
871 if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
872 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
873
874 const PCM_REPOSITORY* repo;
875
876 try
877 {
878 repo = &getCachedRepository( aRepositoryId );
879 }
880 catch( ... )
881 {
882 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
883 }
884
885 if( repo->package_map.count( aPackageId ) == 0 )
886 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
887
888 const PCM_PACKAGE& pkg = repo->package_list[repo->package_map.at( aPackageId )];
889
890 if( installed )
891 {
892 // Package is installed, check for available updates at the same or
893 // higher (numerically lower) version stability level
894 wxString update_version = GetPackageUpdateVersion( pkg );
895
896 return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
897 }
898 else
899 {
900 // Find any compatible version
901 auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
902 []( const PACKAGE_VERSION& ver )
903 {
904 return ver.compatible;
905 } );
906
907 return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
908 }
909}
910
911
913{
914 wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
915 wxT( "GetPackageUpdateVersion called on a not installed package" ) );
916
917 const PCM_INSTALLATION_ENTRY& entry = m_installed.at( aPackage.identifier );
918
919 auto installed_ver_it = std::find_if(
920 entry.package.versions.begin(), entry.package.versions.end(),
921 [&]( const PACKAGE_VERSION& ver )
922 {
923 return ver.version == entry.current_version;
924 } );
925
926 wxASSERT_MSG( installed_ver_it != entry.package.versions.end(),
927 wxT( "Installed package version not found" ) );
928
929 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
930 [&]( const PACKAGE_VERSION& ver )
931 {
932 return ver.compatible
933 && installed_ver_it->status >= ver.status
934 && installed_ver_it->parsed_version < ver.parsed_version;
935 } );
936
937 return ver_it == aPackage.versions.end() ? wxString( wxT( "" ) ) : ver_it->version;
938}
939
941{
942 return std::chrono::duration_cast<std::chrono::seconds>(
943 std::chrono::system_clock::now().time_since_epoch() ).count();
944}
945
946
948{
949 try
950 {
951 nlohmann::json js;
952 js["packages"] = nlohmann::json::array();
953
954 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
955 {
956 js["packages"].emplace_back( pair.second );
957 }
958
959 wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
960 std::ofstream stream( f.GetFullPath().fn_str() );
961
962 stream << std::setw( 4 ) << js << std::endl;
963 }
964 catch( nlohmann::detail::exception& )
965 {
966 // Ignore
967 }
968}
969
970
971const std::vector<PCM_INSTALLATION_ENTRY> PLUGIN_CONTENT_MANAGER::GetInstalledPackages() const
972{
973 std::vector<PCM_INSTALLATION_ENTRY> v;
974
975 std::for_each( m_installed.begin(), m_installed.end(),
976 [&v]( const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
977 {
978 v.push_back( entry.second );
979 } );
980
981 std::sort( v.begin(), v.end(),
982 []( const PCM_INSTALLATION_ENTRY& a, const PCM_INSTALLATION_ENTRY& b )
983 {
984 return ( a.install_timestamp < b.install_timestamp )
985 || ( a.install_timestamp == b.install_timestamp
986 && a.package.identifier < b.package.identifier );
987 } );
988
989 return v;
990}
991
992
993const wxString&
994PLUGIN_CONTENT_MANAGER::GetInstalledPackageVersion( const wxString& aPackageId ) const
995{
996 wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
997 wxT( "Installed package not found." ) );
998
999 return m_installed.at( aPackageId ).current_version;
1000}
1001
1002
1003bool PLUGIN_CONTENT_MANAGER::IsPackagePinned( const wxString& aPackageId ) const
1004{
1005 if( m_installed.find( aPackageId ) == m_installed.end() )
1006 return false;
1007
1008 return m_installed.at( aPackageId ).pinned;
1009}
1010
1011
1012void PLUGIN_CONTENT_MANAGER::SetPinned( const wxString& aPackageId, const bool aPinned )
1013{
1014 if( m_installed.find( aPackageId ) == m_installed.end() )
1015 return;
1016
1017 m_installed.at( aPackageId ).pinned = aPinned;
1018}
1019
1020
1022 const wxString& aSearchTerm )
1023{
1024 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS( " " ), wxTOKEN_STRTOK );
1025 int rank = 0;
1026
1027 const auto find_term_matches =
1028 [&]( const wxString& str )
1029 {
1030 int result = 0;
1031 wxString lower = str.Lower();
1032
1033 for( const wxString& term : terms )
1034 {
1035 if( lower.Find( term ) != wxNOT_FOUND )
1036 result += 1;
1037 }
1038
1039 return result;
1040 };
1041
1042 // Match on package id
1043 if( terms.size() == 1 && terms[0] == aPackage.identifier )
1044 rank += 10000;
1045
1046 if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
1047 rank += 1000;
1048
1049 // Match on package name
1050 rank += 500 * find_term_matches( aPackage.name );
1051
1052 // Match on tags
1053 for( const std::string& tag : aPackage.tags )
1054 rank += 100 * find_term_matches( wxString( tag ) );
1055
1056 // Match on package description
1057 rank += 10 * find_term_matches( aPackage.description );
1058 rank += 10 * find_term_matches( aPackage.description_full );
1059
1060 // Match on author/maintainer
1061 rank += find_term_matches( aPackage.author.name );
1062
1063 if( aPackage.maintainer )
1064 rank += 3 * find_term_matches( aPackage.maintainer->name );
1065
1066 // Match on resources
1067 for( const std::pair<const std::string, wxString>& entry : aPackage.resources )
1068 {
1069 rank += find_term_matches( entry.first );
1070 rank += find_term_matches( entry.second );
1071 }
1072
1073 // Match on license
1074 if( terms.size() == 1 && terms[0] == aPackage.license )
1075 rank += 1;
1076
1077 return rank;
1078}
1079
1080
1081std::unordered_map<wxString, wxBitmap>
1083{
1084 std::unordered_map<wxString, wxBitmap> bitmaps;
1085
1086 wxFileName resources_file = wxFileName( PATHS::GetUserCachePath(), wxT( "resources.zip" ) );
1087 resources_file.AppendDir( wxT( "pcm" ) );
1088 resources_file.AppendDir( aRepositoryId );
1089
1090 if( !resources_file.FileExists() )
1091 return bitmaps;
1092
1093 wxFFileInputStream stream( resources_file.GetFullPath() );
1094 wxZipInputStream zip( stream );
1095
1096 if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
1097 return bitmaps;
1098
1099 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
1100 {
1101 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1102 (wxChar) 0 );
1103
1104 if( path_parts.size() != 2 || path_parts[1] != wxT( "icon.png" ) )
1105 continue;
1106
1107 try
1108 {
1109 wxMemoryInputStream image_stream( zip, entry->GetSize() );
1110 wxImage image( image_stream, wxBITMAP_TYPE_PNG );
1111 bitmaps.emplace( path_parts[0], wxBitmap( image ) );
1112 }
1113 catch( ... )
1114 {
1115 // Log and ignore
1116 wxLogTrace( wxT( "Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1117 resources_file.GetFullPath() );
1118 }
1119 }
1120
1121 return bitmaps;
1122}
1123
1124
1125std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps()
1126{
1127 std::unordered_map<wxString, wxBitmap> bitmaps;
1128
1129 wxFileName resources_dir_fn( m_3rdparty_path, wxEmptyString );
1130 resources_dir_fn.AppendDir( wxT( "resources" ) );
1131 wxDir resources_dir( resources_dir_fn.GetPath() );
1132
1133 if( !resources_dir.IsOpened() )
1134 return bitmaps;
1135
1136 wxString subdir;
1137 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1138
1139 while( more )
1140 {
1141 wxFileName icon( resources_dir_fn.GetPath(), wxT( "icon.png" ) );
1142 icon.AppendDir( subdir );
1143
1144 if( icon.FileExists() )
1145 {
1146 wxString actual_package_id = subdir;
1147 actual_package_id.Replace( '_', '.' );
1148
1149 try
1150 {
1151 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1152 bitmaps.emplace( actual_package_id, bitmap );
1153 }
1154 catch( ... )
1155 {
1156 // Log and ignore
1157 wxLogTrace( wxT( "Error loading png bitmap from %s" ), icon.GetFullPath() );
1158 }
1159 }
1160
1161 more = resources_dir.GetNext( &subdir );
1162 }
1163
1164 return bitmaps;
1165}
1166
1167
1169{
1170 UPDATE_CANCELLER( std::shared_ptr<BACKGROUND_JOB>& aJob ) : m_jobToCancel( aJob ) {};
1172 {
1173 if( m_jobToCancel )
1174 {
1176 m_jobToCancel.reset();
1177 }
1178 }
1179
1180 std::shared_ptr<BACKGROUND_JOB>& m_jobToCancel;
1181};
1182
1183
1185{
1186 // If the thread is already running don't create it again
1187 if( m_updateThread.joinable() )
1188 return;
1189
1190 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "PCM Update" ) );
1191
1192 m_updateThread = std::thread(
1193 [this]()
1194 {
1196
1197 if( m_installed.size() == 0 )
1198 return;
1199
1200 int maxProgress = m_repository_list.size() + m_installed.size();
1201 m_updateBackgroundJob->m_reporter->SetNumPhases( maxProgress );
1202 m_updateBackgroundJob->m_reporter->Report( _( "Preparing to fetch repositories" ) );
1203
1204 // Only fetch repositories that have installed not pinned packages
1205 std::unordered_set<wxString> repo_ids;
1206
1207 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1208 {
1209 if( !pair.second.pinned )
1210 repo_ids.insert( pair.second.repository_id );
1211 }
1212
1213 for( const auto& [ repository_id, name, url ] : m_repository_list )
1214 {
1215 m_updateBackgroundJob->m_reporter->AdvancePhase();
1216 if( repo_ids.count( repository_id ) == 0 )
1217 continue;
1218
1219 m_updateBackgroundJob->m_reporter->Report(
1220 _( "Fetching repository..." ) );
1221 CacheRepository( repository_id );
1222
1223 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1224 break;
1225 }
1226
1227 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1228 return;
1229
1230 // Count packages with updates
1231 int availableUpdateCount = 0;
1232
1233 m_updateBackgroundJob->m_reporter->Report( _( "Reviewing packages..." ) );
1234 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1235 {
1236 PCM_INSTALLATION_ENTRY& entry = pair.second;
1237
1238 m_updateBackgroundJob->m_reporter->AdvancePhase();
1239
1240 if( m_repository_cache.find( entry.repository_id ) != m_repository_cache.end() )
1241 {
1243 entry.package.identifier );
1244
1245 if( state == PPS_UPDATE_AVAILABLE && !entry.pinned )
1246 availableUpdateCount++;
1247 }
1248
1249 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1250 return;
1251 }
1252
1253 // Update the badge on PCM button
1254 m_availableUpdateCallback( availableUpdateCount );
1255 } );
1256}
1257
1258
1260{
1261 if( m_updateThread.joinable() )
1262 {
1264 m_updateBackgroundJob->m_reporter->Cancel();
1265
1266 m_updateThread.join();
1267 }
1268}
1269
1270
1272{
1273 // By the time object is being destroyed the thread should be
1274 // stopped already but just in case do it here too.
1276}
1277
1278
1280{
1282 return;
1283
1285
1286 if( m_dialog
1287 && wxMessageBox( _( "This plugin requires the KiCad API, which is currently "
1288 "disabled in preferences. Would you like to enable it?" ),
1289 _( "Enable KiCad API" ), wxICON_QUESTION | wxYES_NO, m_dialog )
1290 == wxYES )
1291 {
1293 m_dialog->ParentFrame()->Kiway().CommonSettingsChanged();
1294 }
1295}
const char * name
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.
nlohmann::json Validate(const nlohmann::json &aJson, nlohmann::json_schema::error_handler &aErrorHandler, const nlohmann::json_uri &aInitialUri=nlohmann::json_uri("#")) const
int Perform()
Equivalent to curl_easy_perform.
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
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 SetStallTimeout(long aMinBytesPerSec, long aDurationSecs)
Detect stalled transfers without limiting overall transfer time.
bool SetOutputStream(const std::ostream *aOutput)
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition paths.cpp:126
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition paths.cpp:233
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
Definition paths.cpp:460
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:634
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:541
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:787
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:136
time_t getCurrentTimestamp() const
Definition pcm.cpp:940
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
Definition pcm.cpp:757
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
Definition pcm.cpp:772
DIALOG_PCM * m_dialog
Definition pcm.h:418
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v1_validator
Definition pcm.h:419
bool DownloadToStream(const wxString &aUrl, std::ostream *aOutput, PROGRESS_REPORTER *aReporter, const size_t aSizeLimit=DEFAULT_DOWNLOAD_MEM_LIMIT, const std::string &aAccept="")
Downloads url to an output stream.
Definition pcm.cpp:202
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:324
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
Definition pcm.h:423
bool m_apiEnablePromptNeeded
Definition pcm.h:432
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
Definition pcm.cpp:410
int GetPackageSearchRank(const PCM_PACKAGE &aPackage, const wxString &aSearchTerm)
Get the approximate measure of how much given package matches the search term.
Definition pcm.cpp:1021
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition pcm.cpp:860
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v2_validator
Definition pcm.h:420
bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
Definition pcm.cpp:419
void SaveInstalledPackages()
Saves metadata of installed packages to disk.
Definition pcm.cpp:947
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
Definition pcm.cpp:971
wxString m_3rdparty_path
Definition pcm.h:421
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
Definition pcm.cpp:1012
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
Definition pcm.cpp:657
PLUGIN_CONTENT_MANAGER(std::function< void(int)> aAvailableUpdateCallbac)
Definition pcm.cpp:79
bool fetchPackages(const wxString &aUrl, const std::optional< wxString > &aHash, std::vector< PCM_PACKAGE > &aPackages, PROGRESS_REPORTER *aReporter, int aSchemaVersion=1)
Downloads packages metadata to in memory stream, verifies hash and attempts to parse it.
Definition pcm.cpp:341
std::thread m_updateThread
Definition pcm.h:429
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
Definition pcm.cpp:866
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
Definition pcm.h:426
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
Definition pcm.cpp:1184
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
Definition pcm.cpp:1082
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
Definition pcm.cpp:912
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
Definition pcm.cpp:815
static bool UsesSWIGRuntime(const PCM_PACKAGE &aPackage, const wxString &aVersion)
Returns true if the selected package version requires SWIG.
Definition pcm.cpp:738
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
Definition pcm.cpp:1125
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
Definition pcm.cpp:994
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
Definition pcm.cpp:593
bool IsPackagePinned(const wxString &aPackageId) const
Returns pinned status of a package.
Definition pcm.cpp:1003
static const std::tuple< int, int, int > m_kicad_version
Definition pcm.h:427
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
Definition pcm.h:431
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
Definition pcm.cpp:1259
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT
< Default download limit of 10 Mb to not use too much memory
Definition pcm.h:380
STRING_TUPLE_LIST m_repository_list
Definition pcm.h:424
std::function< void(int)> m_availableUpdateCallback
Definition pcm.h:428
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
Definition pcm.cpp:270
void ShowApiEnablePromptIfNeeded()
Definition pcm.cpp:1279
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
Definition pcm.cpp:801
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
Definition pcm.cpp:397
void ReadEnvVar()
Stores 3rdparty path from environment variables.
Definition pcm.cpp:190
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
Definition pcm.cpp:70
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
Base window classes and related definitions.
Functions related to environment variables, including help functions.
nlohmann::json json
Definition gerbview.cpp:53
static const wxChar tracePcm[]
Flag to enable PCM debugging output.
Definition pcm.cpp:59
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.
Definition env_vars.cpp:86
static const std::string PCM_ACCEPT_V2
Definition pcm.cpp:61
std::vector< std::pair< wxString, wxString > > STRING_PAIR_LIST
Definition pcm.h:78
PCM_PACKAGE_STATE
Definition pcm.h:58
@ PPS_INSTALLED
Definition pcm.h:61
@ PPS_UNAVAILABLE
Definition pcm.h:60
@ PPS_UPDATE_AVAILABLE
Definition pcm.h:64
@ PPS_AVAILABLE
Definition pcm.h:59
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
@ PT_PLUGIN
Definition pcm_data.h:44
@ PT_FAB
Definition pcm_data.h:45
@ PVS_STABLE
Definition pcm_data.h:64
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
< Package version metadataPackage metadata
Definition pcm_data.h:92
wxString version
Definition pcm_data.h:93
PCM_PACKAGE_VERSION_STATUS status
Definition pcm_data.h:99
std::optional< wxString > kicad_version_max
Definition pcm_data.h:102
std::optional< int > version_epoch
Definition pcm_data.h:94
std::vector< std::string > platforms
Definition pcm_data.h:100
wxString kicad_version
Definition pcm_data.h:101
std::tuple< int, int, int, int > parsed_version
Definition pcm_data.h:107
wxString name
Definition pcm_data.h:81
Definition pcm_data.h:159
wxString repository_name
Definition pcm_data.h:163
PCM_PACKAGE package
Definition pcm_data.h:160
uint64_t install_timestamp
Definition pcm_data.h:164
wxString repository_id
Definition pcm_data.h:162
wxString current_version
Definition pcm_data.h:161
bool pinned
Definition pcm_data.h:165
Repository reference to a resource.
Definition pcm_data.h:114
wxString description
Definition pcm_data.h:116
wxString description_full
Definition pcm_data.h:117
wxString identifier
Definition pcm_data.h:118
wxString license
Definition pcm_data.h:123
std::vector< std::string > tags
Definition pcm_data.h:125
STRING_MAP resources
Definition pcm_data.h:124
std::optional< PCM_CONTACT > maintainer
Definition pcm_data.h:122
wxString name
Definition pcm_data.h:115
std::vector< PACKAGE_VERSION > versions
Definition pcm_data.h:127
PCM_PACKAGE_TYPE type
Definition pcm_data.h:119
PCM_CONTACT author
Definition pcm_data.h:121
Package installation entry.
Definition pcm_data.h:142
PCM_RESOURCE_REFERENCE packages
Definition pcm_data.h:144
std::vector< PCM_PACKAGE > package_list
Definition pcm_data.h:151
wxString name
Definition pcm_data.h:143
std::optional< PCM_RESOURCE_REFERENCE > resources
Definition pcm_data.h:145
std::unordered_map< wxString, size_t > package_map
Definition pcm_data.h:153
Repository metadata.
Definition pcm_data.h:133
std::optional< wxString > sha256
Definition pcm_data.h:135
uint64_t update_timestamp
Definition pcm_data.h:136
UPDATE_CANCELLER(std::shared_ptr< BACKGROUND_JOB > &aJob)
Definition pcm.cpp:1170
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
Definition pcm.cpp:1180
wxString result
Test unit parsing edge cases and error handling.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:39
#define PR_CAN_ABORT