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 if( !aAccept.empty() )
240 curl.SetHeader( "Accept", aAccept );
241
242 int code = curl.Perform();
243
244 if( !aReporter->IsCancelled() )
245 aReporter->SetCurrentProgress( 1.0 );
246
247 if( code != CURLE_OK )
248 {
249 if( m_dialog )
250 {
251 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
252 wxMessageBox( _( "Download is too large." ) );
253 else if( code != CURLE_ABORTED_BY_CALLBACK )
254 wxLogError( wxString( curl.GetErrorText( code ) ) );
255 }
256
257 return false;
258 }
259
260 return true;
261}
262
263
264bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository,
265 PROGRESS_REPORTER* aReporter )
266{
267 std::stringstream repository_stream;
268
269 aReporter->SetTitle( _( "Fetching repository" ) );
270
271 if( !DownloadToStream( aUrl, &repository_stream, aReporter, 20480,
272 PCM_ACCEPT_V2 ) )
273 {
274 return false;
275 }
276
277 nlohmann::json repository_json;
278
279 try
280 {
281 repository_stream >> repository_json;
282
283 int schema_version = 1;
284
285 if( repository_json.contains( "schema_version" ) )
286 schema_version = repository_json["schema_version"].get<int>();
287
288 if( schema_version >= 2 )
289 {
290 ValidateJson( repository_json, *m_schema_v2_validator,
291 nlohmann::json_uri( "#/definitions/Repository" ) );
292 }
293 else
294 {
295 ValidateJson( repository_json, *m_schema_v1_validator,
296 nlohmann::json_uri( "#/definitions/Repository" ) );
297 }
298
299 aRepository = repository_json.get<PCM_REPOSITORY>();
300 aRepository.schema_version = schema_version;
301 }
302 catch( const std::exception& e )
303 {
304 if( m_dialog )
305 {
306 wxLogError( _( "Unable to parse repository: %s" ), e.what() );
307 wxLogError( _( "The given repository URL does not look like a valid KiCad package "
308 "repository. Please double check the URL." ) );
309 }
310
311 return false;
312 }
313
314 return true;
315}
316
317
318void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json& aJson,
319 const nlohmann::json_uri& aUri ) const
320{
321 THROWING_ERROR_HANDLER error_handler;
322 m_schema_v2_validator->Validate( aJson, error_handler, aUri );
323}
324
325
326void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json& aJson,
327 const JSON_SCHEMA_VALIDATOR& aValidator,
328 const nlohmann::json_uri& aUri ) const
329{
330 THROWING_ERROR_HANDLER error_handler;
331 aValidator.Validate( aJson, error_handler, aUri );
332}
333
334
335bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString& aUrl,
336 const std::optional<wxString>& aHash,
337 std::vector<PCM_PACKAGE>& aPackages,
338 PROGRESS_REPORTER* aReporter,
339 int aSchemaVersion )
340{
341 std::stringstream packages_stream;
342
343 aReporter->SetTitle( _( "Fetching repository packages" ) );
344
345 if( !DownloadToStream( aUrl, &packages_stream, aReporter, DEFAULT_DOWNLOAD_MEM_LIMIT,
346 PCM_ACCEPT_V2 ) )
347 {
348 if( m_dialog )
349 wxLogError( _( "Unable to load repository packages url." ) );
350
351 return false;
352 }
353
354 std::istringstream isstream( packages_stream.str() );
355
356 if( aHash && !VerifyHash( isstream, *aHash ) )
357 {
358 if( m_dialog )
359 wxLogError( _( "Packages hash doesn't match. Repository may be corrupted." ) );
360
361 return false;
362 }
363
364 try
365 {
366 nlohmann::json packages_json = nlohmann::json::parse( packages_stream.str() );
367
368 const JSON_SCHEMA_VALIDATOR& validator =
369 ( aSchemaVersion >= 2 ) ? *m_schema_v2_validator : *m_schema_v1_validator;
370
371 ValidateJson( packages_json, validator,
372 nlohmann::json_uri( "#/definitions/PackageArray" ) );
373
374 aPackages = packages_json["packages"].get<std::vector<PCM_PACKAGE>>();
375 }
376 catch( std::exception& e )
377 {
378 if( m_dialog )
379 {
380 wxLogError( wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ),
381 e.what() ) );
382 }
383
384 return false;
385 }
386
387 return true;
388}
389
390
391bool PLUGIN_CONTENT_MANAGER::VerifyHash( std::istream& aStream, const wxString& aHash ) const
392{
393 std::vector<unsigned char> bytes( picosha2::k_digest_size );
394
395 picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
396 bytes.begin(), bytes.end() );
397 std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
398
399 return aHash.compare( hex_str ) == 0;
400}
401
402
403const PCM_REPOSITORY&
404PLUGIN_CONTENT_MANAGER::getCachedRepository( const wxString& aRepositoryId ) const
405{
406 wxASSERT_MSG( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end(),
407 wxT( "Repository is not cached." ) );
408
409 return m_repository_cache.at( aRepositoryId );
410}
411
412
413bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryId )
414{
415 if( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end() )
416 return true;
417
418 const auto repository_tuple =
419 std::find_if( m_repository_list.begin(), m_repository_list.end(),
420 [&aRepositoryId]( const std::tuple<wxString, wxString, wxString>& t )
421 {
422 return std::get<0>( t ) == aRepositoryId;
423 } );
424
425 if( repository_tuple == m_repository_list.end() )
426 return false;
427
428 wxString url = std::get<2>( *repository_tuple );
429
430 nlohmann::json js;
431 PCM_REPOSITORY current_repo;
432 PCM_REPOSITORY& current_repo_ref = 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, wxT( "" ), 1, PR_CAN_ABORT );
438 else
439 reporter = m_updateBackgroundJob->m_reporter;
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().fn_str() );
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().fn_str() );
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 current_repo.schema_version ) )
505 {
506 return false;
507 }
508
509 for( size_t i = 0; i < current_repo.package_list.size(); i++ )
510 {
511 PreparePackage( current_repo.package_list[i] );
512 current_repo.package_map[current_repo.package_list[i].identifier] = i;
513 }
514
515 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
516
517 std::ofstream repo_cache_stream( repo_cache.GetFullPath().fn_str() );
518 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
519
520 std::ofstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
521 js.clear();
522 js["packages"] = nlohmann::json( current_repo.package_list );
523 packages_cache_stream << std::setw( 4 ) << js << std::endl;
524
525 m_repository_cache[aRepositoryId] = std::move( current_repo );
526 current_repo_ref = m_repository_cache[aRepositoryId];
527 }
528
529 if( current_repo_ref.resources )
530 {
531 // Check resources file date, redownload if needed
532 PCM_RESOURCE_REFERENCE& resources = *current_repo_ref.resources;
533
534 wxFileName resource_file( repo_cache.GetPath(), wxT( "resources.zip" ) );
535
536 time_t mtime = 0;
537
538 if( resource_file.FileExists() )
539 mtime = wxFileModificationTime( resource_file.GetFullPath() );
540
541 if( mtime + 600 < getCurrentTimestamp() && mtime < (time_t) resources.update_timestamp )
542 {
543 std::ofstream resources_stream( resource_file.GetFullPath().fn_str(),
544 std::ios_base::binary );
545
546 reporter->SetTitle( _( "Downloading resources" ) );
547
548 // 100 Mb resource file limit
549 bool success = DownloadToStream( resources.url, &resources_stream, reporter.get(),
550 100 * 1024 * 1024 );
551
552 resources_stream.close();
553
554 if( success )
555 {
556 std::ifstream read_stream( resource_file.GetFullPath().fn_str(),
557 std::ios_base::binary );
558
559
560 if( resources.sha256 && !VerifyHash( read_stream, *resources.sha256 ) )
561 {
562 read_stream.close();
563
564 if( m_dialog )
565 {
566 wxLogError( _( "Resources file hash doesn't match and will not be used. "
567 "Repository may be corrupted." ) );
568 }
569
570 wxRemoveFile( resource_file.GetFullPath() );
571 }
572 }
573 else
574 {
575 // Not critical, just clean up the file
576 wxRemoveFile( resource_file.GetFullPath() );
577 }
578 }
579 }
580
581 updateInstalledPackagesMetadata( aRepositoryId );
582
583 return true;
584}
585
586
588{
589 const PCM_REPOSITORY* repository;
590
591 try
592 {
593 repository = &getCachedRepository( aRepositoryId );
594 }
595 catch( ... )
596 {
597 wxLogTrace( tracePcm, wxS( "Invalid/Missing repository " ) + aRepositoryId );
598 return;
599 }
600
601 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
602 {
603 PCM_INSTALLATION_ENTRY& entry = pair.second;
604
605 // If current package is not from this repository, skip it
606 if( entry.repository_id != aRepositoryId )
607 continue;
608
609 // If current package is no longer in this repository, keep it as is
610 if( repository->package_map.count( entry.package.identifier ) == 0 )
611 continue;
612
613 std::optional<PACKAGE_VERSION> current_version;
614
615 auto current_version_it =
616 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
617 [&]( const PACKAGE_VERSION& version )
618 {
619 return version.version == entry.current_version;
620 } );
621
622 if( current_version_it != entry.package.versions.end() )
623 current_version = *current_version_it; // copy
624
625 // Copy repository metadata into installation entry
626 entry.package = repository->package_list[repository->package_map.at( entry.package.identifier )];
627
628 // Insert current version if it's missing from repository metadata
629 current_version_it =
630 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
631 [&]( const PACKAGE_VERSION& version )
632 {
633 return version.version == entry.current_version;
634 } );
635
636 if( current_version_it == entry.package.versions.end() )
637 {
638 entry.package.versions.emplace_back( *current_version );
639
640 // Re-sort the versions by descending version
641 std::sort( entry.package.versions.begin(), entry.package.versions.end(),
642 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
643 {
644 return a.parsed_version > b.parsed_version;
645 } );
646 }
647 }
648}
649
650
652{
653 // Parse package version strings
654 for( PACKAGE_VERSION& ver : aPackage.versions )
655 {
656 int epoch = 0, major = 0, minor = 0, patch = 0;
657
658 if( ver.version_epoch )
659 epoch = *ver.version_epoch;
660
661 wxStringTokenizer version_tokenizer( ver.version, "." );
662
663 major = wxAtoi( version_tokenizer.GetNextToken() );
664
665 if( version_tokenizer.HasMoreTokens() )
666 minor = wxAtoi( version_tokenizer.GetNextToken() );
667
668 if( version_tokenizer.HasMoreTokens() )
669 patch = wxAtoi( version_tokenizer.GetNextToken() );
670
671 ver.parsed_version = std::make_tuple( epoch, major, minor, patch );
672
673 // Determine compatibility
674 ver.compatible = true;
675
676 auto parse_version_tuple =
677 []( const wxString& version, int deflt )
678 {
679 int ver_major = deflt;
680 int ver_minor = deflt;
681 int ver_patch = deflt;
682
683 wxStringTokenizer tokenizer( version, "." );
684
685 ver_major = wxAtoi( tokenizer.GetNextToken() );
686
687 if( tokenizer.HasMoreTokens() )
688 ver_minor = wxAtoi( tokenizer.GetNextToken() );
689
690 if( tokenizer.HasMoreTokens() )
691 ver_patch = wxAtoi( tokenizer.GetNextToken() );
692
693 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
694 };
695
696 if( parse_version_tuple( ver.kicad_version, 0 ) > m_kicad_version )
697 ver.compatible = false;
698
699 if( ver.kicad_version_max
700 && parse_version_tuple( *ver.kicad_version_max, 999 ) < m_kicad_version )
701 ver.compatible = false;
702
703#if defined( _WIN32 )
704 wxString platform = wxT( "windows" );
705#elif defined( __APPLE__ )
706 wxString platform = wxT( "macos" );
707#else
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}
726
727
728const std::vector<PCM_PACKAGE>&
729PLUGIN_CONTENT_MANAGER::GetRepositoryPackages( const wxString& aRepositoryId ) const
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}
742
743
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}
771
772
773void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepositoryId )
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}
785
786
787void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
788 const wxString& aRepositoryId )
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
821 if( m_dialog
822 && ( aPackage.versions[0].runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_IPC )
823 && !Pgm().GetCommonSettings()->m_Api.enable_server )
824 {
825 // Defer the prompt until after installation completes
826 // to avoid UI operations during wxSafeYield
828 }
829}
830
831
833{
834 m_installed.erase( aPackage.identifier );
835}
836
837
839 const wxString& aPackageId )
840{
841 bool installed = m_installed.find( aPackageId ) != m_installed.end();
842
843 if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
844 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
845
846 const PCM_REPOSITORY* repo;
847
848 try
849 {
850 repo = &getCachedRepository( aRepositoryId );
851 }
852 catch( ... )
853 {
854 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
855 }
856
857 if( repo->package_map.count( aPackageId ) == 0 )
858 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
859
860 const PCM_PACKAGE& pkg = repo->package_list[repo->package_map.at( aPackageId )];
861
862 if( installed )
863 {
864 // Package is installed, check for available updates at the same or
865 // higher (numerically lower) version stability level
866 wxString update_version = GetPackageUpdateVersion( pkg );
867
868 return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
869 }
870 else
871 {
872 // Find any compatible version
873 auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
874 []( const PACKAGE_VERSION& ver )
875 {
876 return ver.compatible;
877 } );
878
879 return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
880 }
881}
882
883
885{
886 wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
887 wxT( "GetPackageUpdateVersion called on a not installed package" ) );
888
889 const PCM_INSTALLATION_ENTRY& entry = m_installed.at( aPackage.identifier );
890
891 auto installed_ver_it = std::find_if(
892 entry.package.versions.begin(), entry.package.versions.end(),
893 [&]( const PACKAGE_VERSION& ver )
894 {
895 return ver.version == entry.current_version;
896 } );
897
898 wxASSERT_MSG( installed_ver_it != entry.package.versions.end(),
899 wxT( "Installed package version not found" ) );
900
901 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
902 [&]( const PACKAGE_VERSION& ver )
903 {
904 return ver.compatible
905 && installed_ver_it->status >= ver.status
906 && installed_ver_it->parsed_version < ver.parsed_version;
907 } );
908
909 return ver_it == aPackage.versions.end() ? wxString( wxT( "" ) ) : ver_it->version;
910}
911
913{
914 return std::chrono::duration_cast<std::chrono::seconds>(
915 std::chrono::system_clock::now().time_since_epoch() ).count();
916}
917
918
920{
921 try
922 {
923 nlohmann::json js;
924 js["packages"] = nlohmann::json::array();
925
926 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
927 {
928 js["packages"].emplace_back( pair.second );
929 }
930
931 wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
932 std::ofstream stream( f.GetFullPath().fn_str() );
933
934 stream << std::setw( 4 ) << js << std::endl;
935 }
936 catch( nlohmann::detail::exception& )
937 {
938 // Ignore
939 }
940}
941
942
943const std::vector<PCM_INSTALLATION_ENTRY> PLUGIN_CONTENT_MANAGER::GetInstalledPackages() const
944{
945 std::vector<PCM_INSTALLATION_ENTRY> v;
946
947 std::for_each( m_installed.begin(), m_installed.end(),
948 [&v]( const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
949 {
950 v.push_back( entry.second );
951 } );
952
953 std::sort( v.begin(), v.end(),
954 []( const PCM_INSTALLATION_ENTRY& a, const PCM_INSTALLATION_ENTRY& b )
955 {
956 return ( a.install_timestamp < b.install_timestamp )
957 || ( a.install_timestamp == b.install_timestamp
958 && a.package.identifier < b.package.identifier );
959 } );
960
961 return v;
962}
963
964
965const wxString&
966PLUGIN_CONTENT_MANAGER::GetInstalledPackageVersion( const wxString& aPackageId ) const
967{
968 wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
969 wxT( "Installed package not found." ) );
970
971 return m_installed.at( aPackageId ).current_version;
972}
973
974
975bool PLUGIN_CONTENT_MANAGER::IsPackagePinned( const wxString& aPackageId ) const
976{
977 if( m_installed.find( aPackageId ) == m_installed.end() )
978 return false;
979
980 return m_installed.at( aPackageId ).pinned;
981}
982
983
984void PLUGIN_CONTENT_MANAGER::SetPinned( const wxString& aPackageId, const bool aPinned )
985{
986 if( m_installed.find( aPackageId ) == m_installed.end() )
987 return;
988
989 m_installed.at( aPackageId ).pinned = aPinned;
990}
991
992
994 const wxString& aSearchTerm )
995{
996 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS( " " ), wxTOKEN_STRTOK );
997 int rank = 0;
998
999 const auto find_term_matches =
1000 [&]( const wxString& str )
1001 {
1002 int result = 0;
1003 wxString lower = str.Lower();
1004
1005 for( const wxString& term : terms )
1006 {
1007 if( lower.Find( term ) != wxNOT_FOUND )
1008 result += 1;
1009 }
1010
1011 return result;
1012 };
1013
1014 // Match on package id
1015 if( terms.size() == 1 && terms[0] == aPackage.identifier )
1016 rank += 10000;
1017
1018 if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
1019 rank += 1000;
1020
1021 // Match on package name
1022 rank += 500 * find_term_matches( aPackage.name );
1023
1024 // Match on tags
1025 for( const std::string& tag : aPackage.tags )
1026 rank += 100 * find_term_matches( wxString( tag ) );
1027
1028 // Match on package description
1029 rank += 10 * find_term_matches( aPackage.description );
1030 rank += 10 * find_term_matches( aPackage.description_full );
1031
1032 // Match on author/maintainer
1033 rank += find_term_matches( aPackage.author.name );
1034
1035 if( aPackage.maintainer )
1036 rank += 3 * find_term_matches( aPackage.maintainer->name );
1037
1038 // Match on resources
1039 for( const std::pair<const std::string, wxString>& entry : aPackage.resources )
1040 {
1041 rank += find_term_matches( entry.first );
1042 rank += find_term_matches( entry.second );
1043 }
1044
1045 // Match on license
1046 if( terms.size() == 1 && terms[0] == aPackage.license )
1047 rank += 1;
1048
1049 return rank;
1050}
1051
1052
1053std::unordered_map<wxString, wxBitmap>
1055{
1056 std::unordered_map<wxString, wxBitmap> bitmaps;
1057
1058 wxFileName resources_file = wxFileName( PATHS::GetUserCachePath(), wxT( "resources.zip" ) );
1059 resources_file.AppendDir( wxT( "pcm" ) );
1060 resources_file.AppendDir( aRepositoryId );
1061
1062 if( !resources_file.FileExists() )
1063 return bitmaps;
1064
1065 wxFFileInputStream stream( resources_file.GetFullPath() );
1066 wxZipInputStream zip( stream );
1067
1068 if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
1069 return bitmaps;
1070
1071 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
1072 {
1073 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1074 (wxChar) 0 );
1075
1076 if( path_parts.size() != 2 || path_parts[1] != wxT( "icon.png" ) )
1077 continue;
1078
1079 try
1080 {
1081 wxMemoryInputStream image_stream( zip, entry->GetSize() );
1082 wxImage image( image_stream, wxBITMAP_TYPE_PNG );
1083 bitmaps.emplace( path_parts[0], wxBitmap( image ) );
1084 }
1085 catch( ... )
1086 {
1087 // Log and ignore
1088 wxLogTrace( wxT( "Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1089 resources_file.GetFullPath() );
1090 }
1091 }
1092
1093 return bitmaps;
1094}
1095
1096
1097std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps()
1098{
1099 std::unordered_map<wxString, wxBitmap> bitmaps;
1100
1101 wxFileName resources_dir_fn( m_3rdparty_path, wxEmptyString );
1102 resources_dir_fn.AppendDir( wxT( "resources" ) );
1103 wxDir resources_dir( resources_dir_fn.GetPath() );
1104
1105 if( !resources_dir.IsOpened() )
1106 return bitmaps;
1107
1108 wxString subdir;
1109 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1110
1111 while( more )
1112 {
1113 wxFileName icon( resources_dir_fn.GetPath(), wxT( "icon.png" ) );
1114 icon.AppendDir( subdir );
1115
1116 if( icon.FileExists() )
1117 {
1118 wxString actual_package_id = subdir;
1119 actual_package_id.Replace( '_', '.' );
1120
1121 try
1122 {
1123 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1124 bitmaps.emplace( actual_package_id, bitmap );
1125 }
1126 catch( ... )
1127 {
1128 // Log and ignore
1129 wxLogTrace( wxT( "Error loading png bitmap from %s" ), icon.GetFullPath() );
1130 }
1131 }
1132
1133 more = resources_dir.GetNext( &subdir );
1134 }
1135
1136 return bitmaps;
1137}
1138
1139
1141{
1142 UPDATE_CANCELLER( std::shared_ptr<BACKGROUND_JOB>& aJob ) : m_jobToCancel( aJob ) {};
1144 {
1145 if( m_jobToCancel )
1146 {
1148 m_jobToCancel.reset();
1149 }
1150 }
1151
1152 std::shared_ptr<BACKGROUND_JOB>& m_jobToCancel;
1153};
1154
1155
1157{
1158 // If the thread is already running don't create it again
1159 if( m_updateThread.joinable() )
1160 return;
1161
1162 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "PCM Update" ) );
1163
1164 m_updateThread = std::thread(
1165 [this]()
1166 {
1168
1169 if( m_installed.size() == 0 )
1170 return;
1171
1172 int maxProgress = m_repository_list.size() + m_installed.size();
1173 m_updateBackgroundJob->m_reporter->SetNumPhases( maxProgress );
1174 m_updateBackgroundJob->m_reporter->Report( _( "Preparing to fetch repositories" ) );
1175
1176 // Only fetch repositories that have installed not pinned packages
1177 std::unordered_set<wxString> repo_ids;
1178
1179 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1180 {
1181 if( !pair.second.pinned )
1182 repo_ids.insert( pair.second.repository_id );
1183 }
1184
1185 for( const auto& [ repository_id, name, url ] : m_repository_list )
1186 {
1187 m_updateBackgroundJob->m_reporter->AdvancePhase();
1188 if( repo_ids.count( repository_id ) == 0 )
1189 continue;
1190
1191 m_updateBackgroundJob->m_reporter->Report(
1192 _( "Fetching repository..." ) );
1193 CacheRepository( repository_id );
1194
1195 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1196 break;
1197 }
1198
1199 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1200 return;
1201
1202 // Count packages with updates
1203 int availableUpdateCount = 0;
1204
1205 m_updateBackgroundJob->m_reporter->Report( _( "Reviewing packages..." ) );
1206 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1207 {
1208 PCM_INSTALLATION_ENTRY& entry = pair.second;
1209
1210 m_updateBackgroundJob->m_reporter->AdvancePhase();
1211
1212 if( m_repository_cache.find( entry.repository_id ) != m_repository_cache.end() )
1213 {
1215 entry.package.identifier );
1216
1217 if( state == PPS_UPDATE_AVAILABLE && !entry.pinned )
1218 availableUpdateCount++;
1219 }
1220
1221 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1222 return;
1223 }
1224
1225 // Update the badge on PCM button
1226 m_availableUpdateCallback( availableUpdateCount );
1227 } );
1228}
1229
1230
1232{
1233 if( m_updateThread.joinable() )
1234 {
1236 m_updateBackgroundJob->m_reporter->Cancel();
1237
1238 m_updateThread.join();
1239 }
1240}
1241
1242
1244{
1245 // By the time object is being destroyed the thread should be
1246 // stopped already but just in case do it here too.
1248}
1249
1250
1252{
1254 return;
1255
1257
1258 if( m_dialog
1259 && wxMessageBox( _( "This plugin requires the KiCad API, which is currently "
1260 "disabled in preferences. Would you like to enable it?" ),
1261 _( "Enable KiCad API" ), wxICON_QUESTION | wxYES_NO, m_dialog )
1262 == wxYES )
1263 {
1265 m_dialog->ParentFrame()->Kiway().CommonSettingsChanged();
1266 }
1267}
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 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:457
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:631
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:547
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:793
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:137
time_t getCurrentTimestamp() const
Definition pcm.cpp:912
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
Definition pcm.cpp:729
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
Definition pcm.cpp:744
DIALOG_PCM * m_dialog
Definition pcm.h:413
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v1_validator
Definition pcm.h:414
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:318
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
Definition pcm.h:418
bool m_apiEnablePromptNeeded
Definition pcm.h:427
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
Definition pcm.cpp:404
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:993
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition pcm.cpp:832
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v2_validator
Definition pcm.h:415
bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
Definition pcm.cpp:413
void SaveInstalledPackages()
Saves metadata of installed packages to disk.
Definition pcm.cpp:919
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
Definition pcm.cpp:943
wxString m_3rdparty_path
Definition pcm.h:416
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
Definition pcm.cpp:984
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
Definition pcm.cpp:651
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:335
std::thread m_updateThread
Definition pcm.h:424
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
Definition pcm.cpp:838
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
Definition pcm.h:421
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
Definition pcm.cpp:1156
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
Definition pcm.cpp:1054
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
Definition pcm.cpp:884
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
Definition pcm.cpp:787
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
Definition pcm.cpp:1097
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
Definition pcm.cpp:966
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
Definition pcm.cpp:587
bool IsPackagePinned(const wxString &aPackageId) const
Returns pinned status of a package.
Definition pcm.cpp:975
static const std::tuple< int, int, int > m_kicad_version
Definition pcm.h:422
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
Definition pcm.h:426
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
Definition pcm.cpp:1231
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT
< Default download limit of 10 Mb to not use too much memory
Definition pcm.h:375
STRING_TUPLE_LIST m_repository_list
Definition pcm.h:419
std::function< void(int)> m_availableUpdateCallback
Definition pcm.h:423
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
Definition pcm.cpp:264
void ShowApiEnablePromptIfNeeded()
Definition pcm.cpp:1251
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
Definition pcm.cpp:773
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
Definition pcm.cpp:391
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:50
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
@ 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_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:1142
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
Definition pcm.cpp:1152
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